From b5ebe78f5efaaf9b4ab6901882fe144965784adc Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 1 Sep 2025 17:22:30 -0700 Subject: [PATCH 01/44] Update all frontend npm dependencies except Svelte and Vite (#3120) * Upgrade node dependencies except Svelte 5 and its peer deps * Fix lint errors * Fix previous Rust deps upgrade breakage * Fix demo artwork * Allow profiling CI workflow to fail --- .devcontainer/devcontainer.json | 1 - .../workflows/comment-profiling-changes.yaml | 17 +- .vscode/extensions.json | 1 - frontend/.eslintrc.cjs | 109 - frontend/eslint.config.js | 125 + frontend/package-lock.json | 3800 +++++++++++------ frontend/package.json | 42 +- frontend/src/components/Editor.svelte | 5 +- .../floating-menus/ColorPicker.svelte | 4 +- .../src/components/panels/Document.svelte | 8 +- frontend/src/components/panels/Layers.svelte | 8 +- frontend/src/components/views/Graph.svelte | 20 +- .../components/widgets/WidgetSection.svelte | 8 +- .../src/components/widgets/WidgetTable.svelte | 20 +- .../widgets/inputs/CurveInput.svelte | 2 +- .../widgets/inputs/FieldInput.svelte | 8 +- .../widgets/inputs/NumberInput.svelte | 2 +- .../widgets/labels/UserInputLabel.svelte | 2 +- .../window/status-bar/StatusBar.svelte | 2 +- .../title-bar/WindowButtonsLinux.svelte | 4 +- .../title-bar/WindowButtonsWindows.svelte | 4 +- .../components/window/workspace/Panel.svelte | 54 +- frontend/src/editor.ts | 3 +- frontend/src/messages.ts | 12 - frontend/src/state-providers/app-window.ts | 3 - frontend/src/state-providers/dialog.ts | 1 - frontend/src/state-providers/document.ts | 1 - frontend/src/state-providers/fonts.ts | 1 - frontend/src/state-providers/fullscreen.ts | 11 +- frontend/src/state-providers/node-graph.ts | 1 - frontend/src/state-providers/portfolio.ts | 7 +- frontend/src/subscription-router.ts | 1 - frontend/src/utility-functions/debounce.ts | 1 - frontend/src/utility-functions/icons.ts | 2 - frontend/src/utility-functions/images.ts | 2 - frontend/src/utility-functions/panic-proxy.ts | 3 - frontend/tsconfig.json | 6 +- frontend/vite.config.ts | 15 +- .../benches/compile_demo_art_iai.rs | 4 +- 39 files changed, 2704 insertions(+), 1616 deletions(-) delete mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/eslint.config.js diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6b4ee383f2..bddfffaf1f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -23,7 +23,6 @@ "streetsidesoftware.code-spell-checker", // Helpful "mhutchie.git-graph", - "waderyan.gitblame", "qezhu.gitlink", "wmaurer.change-case" ] diff --git a/.github/workflows/comment-profiling-changes.yaml b/.github/workflows/comment-profiling-changes.yaml index e51341538c..7dec9532e5 100644 --- a/.github/workflows/comment-profiling-changes.yaml +++ b/.github/workflows/comment-profiling-changes.yaml @@ -1,7 +1,7 @@ name: Profiling Changes on: - pull_request: + pull_request: {} env: CARGO_TERM_COLOR: always @@ -9,6 +9,7 @@ env: jobs: profile: runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v4 with: @@ -63,7 +64,7 @@ jobs: run: | # Compile benchmarks cargo bench --bench compile_demo_art_iai -- --save-baseline=master - + # Runtime benchmarks cargo bench --bench update_executor_iai -- --save-baseline=master cargo bench --bench run_once_iai -- --save-baseline=master @@ -78,25 +79,25 @@ jobs: run: | # Compile benchmarks COMPILE_OUTPUT=$(cargo bench --bench compile_demo_art_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g') - + # Runtime benchmarks UPDATE_OUTPUT=$(cargo bench --bench update_executor_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g') RUN_ONCE_OUTPUT=$(cargo bench --bench run_once_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g') RUN_CACHED_OUTPUT=$(cargo bench --bench run_cached_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g') - + # Store outputs echo "COMPILE_OUTPUT<> $GITHUB_OUTPUT echo "$COMPILE_OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - + echo "UPDATE_OUTPUT<> $GITHUB_OUTPUT echo "$UPDATE_OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - + echo "RUN_ONCE_OUTPUT<> $GITHUB_OUTPUT echo "$RUN_ONCE_OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - + echo "RUN_CACHED_OUTPUT<> $GITHUB_OUTPUT echo "$RUN_CACHED_OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT @@ -135,7 +136,7 @@ jobs: const updateOutput = JSON.parse(`${{ steps.benchmark.outputs.UPDATE_OUTPUT }}`); const runOnceOutput = JSON.parse(`${{ steps.benchmark.outputs.RUN_ONCE_OUTPUT }}`); const runCachedOutput = JSON.parse(`${{ steps.benchmark.outputs.RUN_CACHED_OUTPUT }}`); - + let significantChanges = false; let commentBody = ""; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e37a0efd11..25148f729c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -13,7 +13,6 @@ "streetsidesoftware.code-spell-checker", // Helpful "mhutchie.git-graph", - "waderyan.gitblame", "qezhu.gitlink", "wmaurer.change-case" ] diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs deleted file mode 100644 index c71b3ff332..0000000000 --- a/frontend/.eslintrc.cjs +++ /dev/null @@ -1,109 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, node: true }, - extends: [ - "eslint:recommended", - "plugin:import/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/typescript", - "plugin:svelte/recommended", - "plugin:svelte/prettier", - "prettier", - ], - plugins: ["import", "@typescript-eslint", "prettier"], - settings: { - "import/parsers": { "@typescript-eslint/parser": [".ts"] }, - "import/resolver": { typescript: true, node: true }, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaVersion: "latest", - project: "./tsconfig.json", - extraFileExtensions: [".svelte"], - }, - ignorePatterns: [ - // Ignore generated directories - "node_modules/", - "dist/", - "pkg/", - "wasm/pkg/", - // Don't ignore JS and TS dotfiles in this folder - "!.*.js", - "!.*.ts", - ], - overrides: [ - { - files: ["*.svelte"], - parser: "svelte-eslint-parser", - // Parse the ` - {#each widgetData.tableWidgets as row} - - {#each row as cell} - - {/each} - - {/each} + + {#each widgetData.tableWidgets as row} + + {#each row as cell} + + {/each} + + {/each} +
- -
+ +
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 571173790c..42d3d09c65 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 @@ -15,7 +15,7 @@ use crate::{ transform::ApplyTransform, vector::{ Vector, - style::{Fill, Stroke}, + style::{Fill, Stroke, StrokeAlign}, }, }; @@ -102,16 +102,17 @@ pub fn draw_nodes(nodes: &Vec) -> Table { let border_mask_table = Table::new_from_row(border_mask_row); node_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table))); - // Border table is implemented as a clip mask + // Border is implemented as a clip mask let mut border_table = Table::new(); let mut border_vector = Vector::from_bezpath(node_bez_path); - let primary_output_color = frontend_node + let border_color = frontend_node .primary_output .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 stroke = Stroke::new(Some(border_color), 1.); + // stroke.align = StrokeAlign::Inside; + border_vector.style.stroke = Some(stroke); let mut border_vector_row = TableRow::new_from_element(border_vector); border_vector_row.alpha_blending.clip = true; border_table.push(border_vector_row); @@ -182,22 +183,22 @@ pub fn draw_nodes(nodes: &Vec) -> Table { // Input and output ports let mut ports_table = Table::new(); if let Some(primary_input) = &frontend_node.primary_input { - let mut row = port_row(&primary_input.data_type); + let mut row = port_row(&primary_input.data_type, primary_input.connected_to_node.is_some()); row.transform = DAffine2::from_translation(DVec2::new(0., 12.)); ports_table.push(row); } for (index, secondary_input) in frontend_node.secondary_inputs.iter().enumerate() { - let mut row = port_row(&secondary_input.data_type); + let mut row = port_row(&secondary_input.data_type, secondary_input.connected_to_node.is_some()); row.transform = DAffine2::from_translation(DVec2::new(0., 12. + GRID_SIZE * (index + 1) as f64)); ports_table.push(row); } if let Some(primary_output) = &frontend_node.primary_output { - let mut row = port_row(&primary_output.data_type); + let mut row = port_row(&primary_output.data_type, true); row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12.)); ports_table.push(row); } for (index, secondary_output) in frontend_node.secondary_outputs.iter().enumerate() { - let mut row = port_row(&secondary_output.data_type); + let mut row = port_row(&secondary_output.data_type, true); row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12. + GRID_SIZE * (index + 1) as f64)); ports_table.push(row); } @@ -212,7 +213,7 @@ pub fn draw_nodes(nodes: &Vec) -> Table { pub fn draw_layers(nodes: &Vec) -> (Table, Table) { let mut layer_table = Table::new(); - let mut side_ports = Table::new(); + let mut side_ports_table = Table::new(); for node_to_render in nodes { if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() { // The layer position is the top left of the thumbnail @@ -246,16 +247,14 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< 0. }; - // Text starts at thumbnail + left padding - let text_start = 12. + 8.; + let text_left_padding = 8.; let right_text_edge = 8. + text_width; - let rounded_text_edge = (right_text_edge as f64 / 24.).ceil() * 24.; + // Text starts at thumbnail + left padding + let rounded_text_edge = ((12. + right_text_edge as f64) / 24.).ceil() * 24.; - let rounded_layer_width_pixels = rounded_text_edge + 24.; - // add the left thumbnail gap - let layer_right_edge_width = rounded_layer_width_pixels + 12.; + let rounded_layer_width_pixels = rounded_text_edge + 12.; - let right_layer_width = layer_right_edge_width.max(4.5 * GRID_SIZE); + let right_layer_width = rounded_layer_width_pixels.max(4.5 * GRID_SIZE); let thumbnail_width = 3. * GRID_SIZE; let full_layer_width = chain_width + thumbnail_width + right_layer_width; @@ -321,7 +320,9 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< 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.)); + let stroke = Stroke::new(Some(border_color), 1.); + // stroke.align = StrokeAlign::Inside; + border_vector.style.stroke = Some(stroke); let mut layer_border_clip = TableRow::new_from_element(border_vector); layer_border_clip.alpha_blending.clip = true; border_table.push(layer_border_clip); @@ -330,7 +331,7 @@ pub fn draw_layers(nodes: &Vec) -> (Table, 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.)); + *text_row.transform = DAffine2::from_translation(layer_position + DVec2::new(thumbnail_width + text_left_padding, 16.)); } let top_layer = text_table; layer_table.push(TableRow::new_from_element(Graphic::Vector(top_layer))); @@ -338,34 +339,40 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< // Ports let mut ports_table = Table::new(); if let Some(side_input) = &frontend_layer.side_input { - let mut port = port_row(&side_input.data_type); + let mut port: TableRow = port_row(&side_input.data_type, side_input.connected_to_node.is_some()); port.transform = DAffine2::from_translation(DVec2::new(layer_position.x - 15., layer_position.y + GRID_SIZE - 4.)); - ports_table.push(port); + side_ports_table.push(port); } let top_port = BezPath::from_svg("M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z").unwrap(); let mut vector = Vector::from_bezpath(top_port); - vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.output.data_type.data_color()).unwrap()); - let mut side_port = TableRow::new_from_element(vector); - side_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.)); - side_ports.push(side_port); + vector.style.fill = Fill::Solid(frontend_layer.output.data_type.data_color()); + let mut top_port = TableRow::new_from_element(vector); + top_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.)); + ports_table.push(top_port); + if frontend_layer.primary_output_connected_to_layer { let top_wire_cap = BezPath::from_svg("M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z").unwrap(); let mut vector = Vector::from_bezpath(top_wire_cap); - vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.output.data_type.data_color_dim()).unwrap()); + vector.style.fill = Fill::Solid(frontend_layer.output.data_type.data_color_dim()); let mut vector_row = TableRow::new_from_element(vector); vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.)); ports_table.push(vector_row); } let bottom_port = BezPath::from_svg("M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z").unwrap(); let mut vector = Vector::from_bezpath(bottom_port); - vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.bottom_input.data_type.data_color()).unwrap()); + let mut bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() { + frontend_layer.bottom_input.data_type.data_color() + } else { + frontend_layer.bottom_input.data_type.data_color_dim() + }; + vector.style.fill = Fill::Solid(bottom_port_fill); let mut vector_row = TableRow::new_from_element(vector); vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y + 2. * GRID_SIZE)); ports_table.push(vector_row); if frontend_layer.primary_input_connected_to_layer { let bottom_port_cap = BezPath::from_svg("M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z").unwrap(); let mut vector = Vector::from_bezpath(bottom_port_cap); - vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.bottom_input.data_type.data_color_dim()).unwrap()); + vector.style.fill = Fill::Solid(frontend_layer.bottom_input.data_type.data_color_dim()); let mut vector_row = TableRow::new_from_element(vector); vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y + 2. * GRID_SIZE)); ports_table.push(vector_row); @@ -406,11 +413,59 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< icons_table.push(grip_row); } layer_table.push(TableRow::new_from_element(Graphic::Vector(icons_table))); + + // Thumbnail border/bg + let border = RoundedRect::new(layer_position.x, layer_position.y, layer_position.x + thumbnail_width, layer_position.y + 2. * GRID_SIZE, 2.); + let mut border_vec = Vector::from_bezpath(border.to_path(BEZ_PATH_TOLERANCE)); + let stroke = Stroke::new(Some(frontend_layer.output.data_type.data_color_dim()), 1.); + // stroke.align = StrokeAlign::Inside; + border_vec.style.stroke = Some(stroke); + border_vec.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(COLOR_2_MILDBLACK).unwrap()); + layer_table.push(TableRow::new_from_element(Graphic::Vector(Table::new_from_element(border_vec)))); + + // Region to display thumbnail + let clip_vector = Vector::from_bezpath( + Rect::new( + layer_position.x + 2., + layer_position.y + 2., + layer_position.x + thumbnail_width - 2., + layer_position.y + GRID_SIZE * 2. - 2., + ) + .to_path(BEZ_PATH_TOLERANCE), + ); + layer_table.push(TableRow::new_from_element(Graphic::Vector(Table::new_from_row(TableRow::new_from_element(clip_vector))))); + + // Inner thumbnail + let mut inner_thumbnail_table = Table::new(); + for col in 0..9 { + for row in 0..6 { + let fill = if (col + row) % 2 == 0 { + Color::from_rgba8_no_srgb(COLOR_C_BRIGHTGRAY).unwrap() + } else { + Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap() + }; + let mut vector = Vector::from_bezpath( + Rect::new( + 2. + 8. * col as f64 + layer_position.x, + 2. + 8. * row as f64 + layer_position.y, + 2. + 8. * col as f64 + layer_position.x + 9., + 2. + 8. * row as f64 + layer_position.y + 9., + ) + .to_path(BEZ_PATH_TOLERANCE), + ); + vector.style.fill = Fill::Solid(fill); + inner_thumbnail_table.push(TableRow::new_from_element(vector)); + } + } + let mut thumbnail_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table)); + thumbnail_row.alpha_blending.clip = true; + let graphic_table = Table::new_from_row(thumbnail_row); + layer_table.push(TableRow::new_from_element(Graphic::Graphic(graphic_table))); } } let mut ports_table = Table::new(); - ports_table.push(TableRow::new_from_element(Graphic::Vector(side_ports))); + ports_table.push(TableRow::new_from_element(Graphic::Vector(side_ports_table))); (layer_table, ports_table) } @@ -455,12 +510,17 @@ fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector { vector } -fn port_row(data_type: &FrontendGraphDataType) -> TableRow { +fn port_row(data_type: &FrontendGraphDataType, full_brightness: bool) -> TableRow { let path = BezPath::from_svg("M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z").unwrap_or_else(|e| { panic!("Could not parse port svg from string: {}", e); }); let mut vector = Vector::from_bezpath(path); - vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(data_type.data_color()).unwrap()); + let fill = if full_brightness { + Fill::Solid(data_type.data_color()) + } else { + Fill::Solid(data_type.data_color_dim()) + }; + vector.style.fill = fill; TableRow::new_from_element(vector) } @@ -470,7 +530,7 @@ pub fn draw_wires(nodes: &mut Vec) -> Table { for (wire_string, thick, data_type) in &mut node.wires { let mut wire_vector = Vector::from_bezpath(std::mem::take(wire_string)); let weight = if *thick { 8. } else { 2. }; - wire_vector.style.set_stroke(Stroke::new(Some(Color::from_rgba8_no_srgb(data_type.data_color_dim()).unwrap()), weight)); + wire_vector.style.set_stroke(Stroke::new(Some(data_type.data_color_dim()), weight)); wire_table.push(TableRow::new_from_element(wire_vector)); } } diff --git a/node-graph/gcore/src/node_graph_overlay/types.rs b/node-graph/gcore/src/node_graph_overlay/types.rs index 1acfcc3a0b..abf537b38b 100644 --- a/node-graph/gcore/src/node_graph_overlay/types.rs +++ b/node-graph/gcore/src/node_graph_overlay/types.rs @@ -1,4 +1,5 @@ use glam::{DAffine2, DVec2}; +use graphene_core_shaders::color::Color; use kurbo::BezPath; use crate::{node_graph_overlay::consts::*, uuid::NodeId}; @@ -214,8 +215,8 @@ pub enum FrontendGraphDataType { } impl FrontendGraphDataType { - pub fn data_color(&self) -> &'static str { - match self { + pub fn data_color(&self) -> Color { + let color_str = match self { FrontendGraphDataType::General => COLOR_DATA_GENERAL, FrontendGraphDataType::Number => COLOR_DATA_NUMBER, FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD, @@ -225,10 +226,11 @@ impl FrontendGraphDataType { FrontendGraphDataType::Color => COLOR_DATA_COLOR, FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT, FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY, - } + }; + Color::from_rgba8_no_srgb(color_str).unwrap() } - pub fn data_color_dim(&self) -> &'static str { - match self { + pub fn data_color_dim(&self) -> Color { + let color_str = match self { FrontendGraphDataType::General => COLOR_DATA_GENERAL_DIM, FrontendGraphDataType::Number => COLOR_DATA_NUMBER_DIM, FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD_DIM, @@ -238,6 +240,7 @@ impl FrontendGraphDataType { FrontendGraphDataType::Color => COLOR_DATA_COLOR_DIM, FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT_DIM, FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY_DIM, - } + }; + Color::from_rgba8_no_srgb(color_str).unwrap() } } From 537395b4f9b3abf80c3140cb3880e56b980d8bbe Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 6 Sep 2025 21:27:24 -0700 Subject: [PATCH 44/44] thumbnails --- .../document/node_graph/node_graph_message.rs | 5 ++ .../node_graph/node_graph_message_handler.rs | 6 ++ editor/src/node_graph_executor.rs | 37 ++++++---- editor/src/node_graph_executor/runtime.rs | 39 +++++----- node-graph/gcore/src/node_graph_overlay.rs | 2 +- .../src/node_graph_overlay/nodes_and_wires.rs | 41 ++++++++--- .../gcore/src/node_graph_overlay/types.rs | 24 ++++++- node-graph/gsvg-renderer/src/renderer.rs | 71 +++++++++++++++++++ 8 files changed, 182 insertions(+), 43 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 6f0b606ed0..06c2e8d2bd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -7,6 +7,7 @@ use glam::IVec2; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graph_craft::proto::GraphErrors; +use graphene_std::Graphic; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; #[impl_message(Message, DocumentMessage, NodeGraph)] @@ -218,6 +219,10 @@ pub enum NodeGraphMessage { UpdateImportsExports, UpdateLayerPanel, UpdateNewNodeGraph, + UpdateThumbnail { + node_id: NodeId, + graphic: Graphic, + }, UpdateTypes { #[serde(skip)] resolved_types: ResolvedDocumentNodeTypesDelta, 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 7076decab2..ff0b70d0f1 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 @@ -92,6 +92,8 @@ pub struct NodeGraphMessageHandler { reordering_export: Option, /// The end index of the moved connector end_index: Option, + // The rendered string for each thumbnail + pub thumbnails: HashMap, } /// 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. @@ -1929,6 +1931,9 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendGraph); } + NodeGraphMessage::UpdateThumbnail { node_id, graphic } => { + self.thumbnails.insert(node_id, graphic); + } NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { network_interface.resolved_types.update(resolved_types); self.node_graph_errors = node_graph_errors; @@ -2599,6 +2604,7 @@ impl Default for NodeGraphMessageHandler { reordering_export: None, reordering_import: None, end_index: None, + thumbnails: HashMap::new(), } } } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 8d609d34d2..19ffb07c59 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -29,10 +29,16 @@ pub struct ExecutionRequest { pub struct ExecutionResponse { execution_id: u64, result: Result, - responses: VecDeque, + execution_responses: Vec, vector_modify: HashMap, +} + +pub enum ExecutionResponseMessage { /// The resulting value from the temporary inspected during execution - inspect_result: Option, + InspectResult(Option), + UpdateNodeGraphThumbnail(NodeId, Graphic), + UpdateFrontendThumbnail(NodeId, String), + SendGraph, } #[derive(serde::Serialize, serde::Deserialize)] @@ -260,11 +266,24 @@ impl NodeGraphExecutor { let ExecutionResponse { execution_id, result, - responses: existing_responses, + execution_responses, vector_modify, - inspect_result, } = execution_response; - + for execution_response in execution_responses { + match execution_response { + ExecutionResponseMessage::InspectResult(inspect_result) => { + // Update the Data panel on the frontend using the value of the inspect result. + if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() { + responses.add(DataPanelMessage::UpdateLayout { inspect_result }); + } else { + responses.add(DataPanelMessage::ClearLayout); + } + } + ExecutionResponseMessage::UpdateNodeGraphThumbnail(node_id, graphic) => responses.add(NodeGraphMessage::UpdateThumbnail { node_id, graphic }), + ExecutionResponseMessage::UpdateFrontendThumbnail(node_id, string) => responses.add(FrontendMessage::UpdateNodeThumbnail { id: node_id, value: string }), + ExecutionResponseMessage::SendGraph => responses.add(NodeGraphMessage::SendGraph), + } + } responses.add(OverlaysMessage::Draw); let node_graph_output = match result { @@ -277,7 +296,6 @@ impl NodeGraphExecutor { } }; - responses.extend(existing_responses.into_iter().map(Into::into)); document.network_interface.update_vector_modify(vector_modify); let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?; @@ -291,13 +309,6 @@ impl NodeGraphExecutor { execution_id, document_id: execution_context.document_id, }); - - // Update the Data panel on the frontend using the value of the inspect result. - if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() { - responses.add(DataPanelMessage::UpdateLayout { inspect_result }); - } else { - responses.add(DataPanelMessage::ClearLayout); - } } NodeGraphUpdate::CompilationResponse(execution_response) => { let CompilationResponse { node_graph_errors, result } = execution_response; diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index b89c5b2f25..ae72db8732 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -212,14 +212,14 @@ impl NodeRuntime { } GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => { let result = self.execute_network(render_config).await; - let mut responses = VecDeque::new(); + let mut execution_responses = Vec::new(); // TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes - self.process_monitor_nodes(&mut responses, self.update_thumbnails); + self.process_monitor_nodes(&mut execution_responses, self.update_thumbnails); self.update_thumbnails = false; // Resolve the result from the inspection by accessing the monitor node let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor)); - + execution_responses.push(ExecutionResponseMessage::InspectResult(inspect_result)); let texture = if let Ok(TaggedValue::RenderOutput(RenderOutput { data: RenderOutputType::Texture(texture), .. @@ -233,9 +233,8 @@ impl NodeRuntime { self.sender.send_execution_response(ExecutionResponse { execution_id, result, - responses, + execution_responses, vector_modify: self.vector_modify.clone(), - inspect_result, }); return texture; } @@ -289,10 +288,10 @@ impl NodeRuntime { } /// Updates state data - pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque, update_thumbnails: bool) { + pub fn process_monitor_nodes(&mut self, responses: &mut Vec, update_thumbnails: bool) { // TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n)) self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); - + let mut updated_thumbnails = false; for monitor_node_path in &self.monitor_nodes { // Skip the inspect monitor node if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) { @@ -316,13 +315,17 @@ impl NodeRuntime { // Graphic table: thumbnail if let Some(io) = introspected_data.downcast_ref::>>() { if update_thumbnails { - Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses) + Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses); + responses.push(ExecutionResponseMessage::UpdateNodeGraphThumbnail(parent_network_node_id, io.output.clone().to_graphic())); + updated_thumbnails = true; } } // Artboard table: thumbnail else if let Some(io) = introspected_data.downcast_ref::>>() { if update_thumbnails { - Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses) + Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses); + responses.push(ExecutionResponseMessage::UpdateNodeGraphThumbnail(parent_network_node_id, io.output.clone().to_graphic())); + updated_thumbnails = true; } } // Vector table: vector modifications @@ -337,18 +340,21 @@ impl NodeRuntime { log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}"); } } + if updated_thumbnails { + responses.push(ExecutionResponseMessage::SendGraph); + } } /// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI. - fn render_thumbnail(thumbnail_renders: &mut HashMap>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque) { + fn render_thumbnail(thumbnail_renders: &mut HashMap>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut Vec) { // Skip thumbnails if the layer is too complex (for performance) if graphic.render_complexity() > 1000 { let old = thumbnail_renders.insert(parent_network_node_id, Vec::new()); if old.is_none_or(|v| !v.is_empty()) { - responses.push_back(FrontendMessage::UpdateNodeThumbnail { - id: parent_network_node_id, - value: "Dense thumbnail omitted for performance".to_string(), - }); + responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail( + parent_network_node_id, + "Dense thumbnail omitted for performance".to_string(), + )); } return; } @@ -382,10 +388,7 @@ impl NodeRuntime { let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default(); if old_thumbnail_svg != &new_thumbnail_svg { - responses.push_back(FrontendMessage::UpdateNodeThumbnail { - id: parent_network_node_id, - value: new_thumbnail_svg.to_svg_string(), - }); + responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail(parent_network_node_id, new_thumbnail_svg.to_svg_string())); *old_thumbnail_svg = new_thumbnail_svg; } } diff --git a/node-graph/gcore/src/node_graph_overlay.rs b/node-graph/gcore/src/node_graph_overlay.rs index 874332d638..ac234a7664 100644 --- a/node-graph/gcore/src/node_graph_overlay.rs +++ b/node-graph/gcore/src/node_graph_overlay.rs @@ -21,7 +21,7 @@ pub mod ui_context; #[node_macro::node(skip_impl)] pub fn generate_nodes(_: impl Ctx, mut node_graph_overlay_data: NodeGraphOverlayData) -> Table { let mut nodes_and_wires = Table::new(); - let (layers, side_ports) = draw_layers(&node_graph_overlay_data.nodes_to_render); + let (layers, side_ports) = draw_layers(&mut node_graph_overlay_data); nodes_and_wires.extend(layers); let wires = draw_wires(&mut node_graph_overlay_data.nodes_to_render); 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 42d3d09c65..1e25b8a5cb 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 @@ -8,13 +8,14 @@ use crate::{ consts::SOURCE_SANS_FONT_DATA, node_graph_overlay::{ consts::*, - types::{FrontendGraphDataType, FrontendNodeToRender}, + types::{FrontendGraphDataType, FrontendNodeToRender, NodeGraphOverlayData}, }, table::{Table, TableRow}, text::{self, TextAlign, TypesettingConfig}, transform::ApplyTransform, vector::{ Vector, + style::{Fill, Stroke}, style::{Fill, Stroke, StrokeAlign}, }, }; @@ -211,10 +212,10 @@ pub fn draw_nodes(nodes: &Vec) -> Table { node_table } -pub fn draw_layers(nodes: &Vec) -> (Table, Table) { +pub fn draw_layers(nodes: &mut NodeGraphOverlayData) -> (Table, Table) { let mut layer_table = Table::new(); let mut side_ports_table = Table::new(); - for node_to_render in nodes { + for node_to_render in &nodes.nodes_to_render { if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() { // The layer position is the top left of the thumbnail let layer_position = DVec2::new(frontend_layer.position.x as f64 * GRID_SIZE + 12., frontend_layer.position.y as f64 * GRID_SIZE); @@ -360,7 +361,7 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< } let bottom_port = BezPath::from_svg("M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z").unwrap(); let mut vector = Vector::from_bezpath(bottom_port); - let mut bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() { + let bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() { frontend_layer.bottom_input.data_type.data_color() } else { frontend_layer.bottom_input.data_type.data_color_dim() @@ -457,10 +458,34 @@ pub fn draw_layers(nodes: &Vec) -> (Table, Table< inner_thumbnail_table.push(TableRow::new_from_element(vector)); } } - let mut thumbnail_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table)); - thumbnail_row.alpha_blending.clip = true; - let graphic_table = Table::new_from_row(thumbnail_row); - layer_table.push(TableRow::new_from_element(Graphic::Graphic(graphic_table))); + let mut thumbnail_grid_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table)); + thumbnail_grid_row.alpha_blending.clip = true; + let mut clipped_thumbnail_table = Table::new(); + clipped_thumbnail_table.push(thumbnail_grid_row); + if let Some(thumbnail_graphic) = nodes.thumbnails.get_mut(&node_to_render.metadata.node_id) { + let thumbnail_graphic = std::mem::take(thumbnail_graphic); + let bbox = thumbnail_graphic.bounding_box(DAffine2::default(), false); + if let RenderBoundingBox::Rectangle(rect) = bbox { + let rect_size = rect[1] - rect[0]; + let target_size = DVec2::new(68., 44.); + // uniform scale that fits in target box + let scale_x = target_size.x / rect_size.x; + let scale_y = target_size.y / rect_size.y; + let scale = scale_x.min(scale_y); + + let translation = rect[0] * -scale; + let scaled_size = rect_size * scale; + let offset_to_center = (target_size - scaled_size) / 2.; + + let mut thumbnail_graphic_row = TableRow::new_from_element(thumbnail_graphic); + thumbnail_graphic_row.transform = DAffine2::from_translation(layer_position + offset_to_center) * DAffine2::from_scale_angle_translation(DVec2::splat(scale), 0., translation); + thumbnail_graphic_row.alpha_blending.clip = true; + + clipped_thumbnail_table.push(thumbnail_graphic_row); + } + } + + layer_table.push(TableRow::new_from_element(Graphic::Graphic(clipped_thumbnail_table))); } } diff --git a/node-graph/gcore/src/node_graph_overlay/types.rs b/node-graph/gcore/src/node_graph_overlay/types.rs index abf537b38b..8df7c0ace6 100644 --- a/node-graph/gcore/src/node_graph_overlay/types.rs +++ b/node-graph/gcore/src/node_graph_overlay/types.rs @@ -2,8 +2,11 @@ use glam::{DAffine2, DVec2}; use graphene_core_shaders::color::Color; use kurbo::BezPath; -use crate::{node_graph_overlay::consts::*, uuid::NodeId}; -use std::hash::{Hash, Hasher}; +use crate::{Graphic, node_graph_overlay::consts::*, uuid::NodeId}; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, +}; #[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] pub struct NodeGraphTransform { @@ -27,13 +30,28 @@ impl NodeGraphTransform { } } -#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] 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, + pub thumbnails: HashMap, +} + +impl Hash for NodeGraphOverlayData { + fn hash(&self, state: &mut H) { + self.nodes_to_render.hash(state); + self.open.hash(state); + self.in_selected_network.hash(state); + self.previewed_node.hash(state); + let mut entries: Vec<_> = self.thumbnails.iter().collect(); + entries.sort_by(|a, b| a.0.cmp(b.0)); + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + entries.hash(&mut hasher); + hasher.finish(); + } } #[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index cb5bfd02a6..efeb98f151 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -9,6 +9,7 @@ use graphene_core::color::Color; use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientType; use graphene_core::math::quad::Quad; +use graphene_core::node_graph_overlay::consts::BEZ_PATH_TOLERANCE; use graphene_core::raster::BitmapMut; use graphene_core::raster::Image; use graphene_core::raster_types::{CPU, GPU, Raster}; @@ -23,6 +24,8 @@ use graphene_core::vector::click_target::{ClickTarget, FreePoint}; use graphene_core::vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, ViewMode}; use graphene_core::{Artboard, Graphic}; use kurbo::Affine; +use kurbo::Rect; +use kurbo::Shape; use num_traits::Zero; use skrifa::MetadataProvider; use skrifa::attribute::Style; @@ -247,6 +250,8 @@ pub trait Render: BoundingBox + RenderComplexity { #[cfg(feature = "vello")] fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams); + fn to_graphic(self) -> Graphic; + /// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection. fn add_upstream_click_targets(&self, _click_targets: &mut Vec) {} @@ -289,6 +294,17 @@ impl Render for Graphic { } } + fn to_graphic(self) -> Graphic { + match self { + Graphic::Graphic(table) => table.to_graphic(), + Graphic::Vector(table) => table.to_graphic(), + Graphic::RasterCPU(table) => table.to_graphic(), + Graphic::RasterGPU(table) => table.to_graphic(), + Graphic::Color(table) => table.to_graphic(), + Graphic::Gradient(table) => table.to_graphic(), + } + } + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { if let Some(element_id) = element_id { match self { @@ -467,6 +483,23 @@ impl Render for Artboard { } } + fn to_graphic(self) -> Graphic { + let bg = Rect::new( + self.location.x as f64, + self.location.y as f64, + self.location.x as f64 + self.dimensions.x as f64, + self.location.y as f64 + self.dimensions.y as f64, + ); + let mut bg_vector = Vector::from_bezpath(bg.to_path(BEZ_PATH_TOLERANCE)); + bg_vector.style.fill = Fill::Solid(self.background); + let mut graphic_table = Table::new(); + graphic_table.push(TableRow::new_from_element(Graphic::Graphic(Table::new_from_element(Graphic::Vector(Table::new_from_element( + bg_vector, + )))))); + graphic_table.push(TableRow::new_from_element(Graphic::Graphic(self.content))); + Graphic::Graphic(graphic_table) + } + fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { if let Some(element_id) = element_id { let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); @@ -505,6 +538,20 @@ impl Render for Table { } } + fn to_graphic(self) -> Graphic { + let mut graphic_table = Table::new(); + for item in self.into_iter() { + let graphic = item.element.to_graphic(); + let graphic_row = TableRow { + element: graphic, + transform: item.transform, + alpha_blending: item.alpha_blending, + source_node_id: item.source_node_id, + }; + graphic_table.push(graphic_row); + } + Graphic::Graphic(graphic_table) + } fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option) { for row in self.iter() { row.element.collect_metadata(metadata, footprint, *row.source_node_id); @@ -643,6 +690,10 @@ impl Render for Table { } } + fn to_graphic(self) -> Graphic { + Graphic::Graphic(self) + } + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { for row in self.iter() { if let Some(element_id) = row.source_node_id { @@ -1141,6 +1192,10 @@ impl Render for Table { } } + fn to_graphic(self) -> Graphic { + Graphic::Vector(self) + } + fn add_upstream_click_targets(&self, click_targets: &mut Vec) { for row in self.iter() { let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width); @@ -1318,6 +1373,10 @@ impl Render for Table> { } } + fn to_graphic(self) -> Graphic { + Graphic::RasterCPU(self) + } + fn add_upstream_click_targets(&self, click_targets: &mut Vec) { let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); click_targets.push(ClickTarget::new_with_subpath(subpath, 0.)); @@ -1364,6 +1423,10 @@ impl Render for Table> { } } + fn to_graphic(self) -> Graphic { + Graphic::RasterGPU(self) + } + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { let Some(element_id) = element_id else { return }; let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); @@ -1444,6 +1507,10 @@ impl Render for Table { } } } + + fn to_graphic(self) -> Graphic { + Graphic::Color(self) + } } impl Render for Table { @@ -1544,6 +1611,10 @@ impl Render for Table { } } } + + fn to_graphic(self) -> Graphic { + Graphic::Gradient(self) + } } impl Render for Table {