Skip to content

Commit 8caf931

Browse files
committed
thumbnails
1 parent a25a23e commit 8caf931

File tree

8 files changed

+182
-43
lines changed

8 files changed

+182
-43
lines changed

editor/src/messages/portfolio/document/node_graph/node_graph_message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use glam::IVec2;
77
use graph_craft::document::value::TaggedValue;
88
use graph_craft::document::{NodeId, NodeInput};
99
use graph_craft::proto::GraphErrors;
10+
use graphene_std::Graphic;
1011
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
1112

1213
#[impl_message(Message, DocumentMessage, NodeGraph)]
@@ -218,6 +219,10 @@ pub enum NodeGraphMessage {
218219
UpdateImportsExports,
219220
UpdateLayerPanel,
220221
UpdateNewNodeGraph,
222+
UpdateThumbnail {
223+
node_id: NodeId,
224+
graphic: Graphic,
225+
},
221226
UpdateTypes {
222227
#[serde(skip)]
223228
resolved_types: ResolvedDocumentNodeTypesDelta,

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ pub struct NodeGraphMessageHandler {
9292
reordering_export: Option<usize>,
9393
/// The end index of the moved connector
9494
end_index: Option<usize>,
95+
// The rendered string for each thumbnail
96+
pub thumbnails: HashMap<NodeId, Graphic>,
9597
}
9698

9799
/// 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.
@@ -1924,6 +1926,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
19241926

19251927
responses.add(NodeGraphMessage::SendGraph);
19261928
}
1929+
NodeGraphMessage::UpdateThumbnail { node_id, graphic } => {
1930+
self.thumbnails.insert(node_id, graphic);
1931+
}
19271932
NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {
19281933
network_interface.resolved_types.update(resolved_types);
19291934
self.node_graph_errors = node_graph_errors;
@@ -2559,6 +2564,7 @@ impl Default for NodeGraphMessageHandler {
25592564
reordering_export: None,
25602565
reordering_import: None,
25612566
end_index: None,
2567+
thumbnails: HashMap::new(),
25622568
}
25632569
}
25642570
}

editor/src/node_graph_executor.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ pub struct ExecutionRequest {
2929
pub struct ExecutionResponse {
3030
execution_id: u64,
3131
result: Result<TaggedValue, String>,
32-
responses: VecDeque<FrontendMessage>,
32+
execution_responses: Vec<ExecutionResponseMessage>,
3333
vector_modify: HashMap<NodeId, Vector>,
34+
}
35+
36+
pub enum ExecutionResponseMessage {
3437
/// The resulting value from the temporary inspected during execution
35-
inspect_result: Option<InspectResult>,
38+
InspectResult(Option<InspectResult>),
39+
UpdateNodeGraphThumbnail(NodeId, Graphic),
40+
UpdateFrontendThumbnail(NodeId, String),
41+
SendGraph,
3642
}
3743

3844
#[derive(serde::Serialize, serde::Deserialize)]
@@ -260,11 +266,24 @@ impl NodeGraphExecutor {
260266
let ExecutionResponse {
261267
execution_id,
262268
result,
263-
responses: existing_responses,
269+
execution_responses,
264270
vector_modify,
265-
inspect_result,
266271
} = execution_response;
267-
272+
for execution_response in execution_responses {
273+
match execution_response {
274+
ExecutionResponseMessage::InspectResult(inspect_result) => {
275+
// Update the Data panel on the frontend using the value of the inspect result.
276+
if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {
277+
responses.add(DataPanelMessage::UpdateLayout { inspect_result });
278+
} else {
279+
responses.add(DataPanelMessage::ClearLayout);
280+
}
281+
}
282+
ExecutionResponseMessage::UpdateNodeGraphThumbnail(node_id, graphic) => responses.add(NodeGraphMessage::UpdateThumbnail { node_id, graphic }),
283+
ExecutionResponseMessage::UpdateFrontendThumbnail(node_id, string) => responses.add(FrontendMessage::UpdateNodeThumbnail { id: node_id, value: string }),
284+
ExecutionResponseMessage::SendGraph => responses.add(NodeGraphMessage::SendGraph),
285+
}
286+
}
268287
responses.add(OverlaysMessage::Draw);
269288

270289
let node_graph_output = match result {
@@ -277,7 +296,6 @@ impl NodeGraphExecutor {
277296
}
278297
};
279298

280-
responses.extend(existing_responses.into_iter().map(Into::into));
281299
document.network_interface.update_vector_modify(vector_modify);
282300

283301
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
@@ -291,13 +309,6 @@ impl NodeGraphExecutor {
291309
execution_id,
292310
document_id: execution_context.document_id,
293311
});
294-
295-
// Update the Data panel on the frontend using the value of the inspect result.
296-
if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {
297-
responses.add(DataPanelMessage::UpdateLayout { inspect_result });
298-
} else {
299-
responses.add(DataPanelMessage::ClearLayout);
300-
}
301312
}
302313
NodeGraphUpdate::CompilationResponse(execution_response) => {
303314
let CompilationResponse { node_graph_errors, result } = execution_response;

editor/src/node_graph_executor/runtime.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,14 @@ impl NodeRuntime {
212212
}
213213
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
214214
let result = self.execute_network(render_config).await;
215-
let mut responses = VecDeque::new();
215+
let mut execution_responses = Vec::new();
216216
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
217-
self.process_monitor_nodes(&mut responses, self.update_thumbnails);
217+
self.process_monitor_nodes(&mut execution_responses, self.update_thumbnails);
218218
self.update_thumbnails = false;
219219

220220
// Resolve the result from the inspection by accessing the monitor node
221221
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor));
222-
222+
execution_responses.push(ExecutionResponseMessage::InspectResult(inspect_result));
223223
let texture = if let Ok(TaggedValue::RenderOutput(RenderOutput {
224224
data: RenderOutputType::Texture(texture),
225225
..
@@ -233,9 +233,8 @@ impl NodeRuntime {
233233
self.sender.send_execution_response(ExecutionResponse {
234234
execution_id,
235235
result,
236-
responses,
236+
execution_responses,
237237
vector_modify: self.vector_modify.clone(),
238-
inspect_result,
239238
});
240239
return texture;
241240
}
@@ -289,10 +288,10 @@ impl NodeRuntime {
289288
}
290289

291290
/// Updates state data
292-
pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<FrontendMessage>, update_thumbnails: bool) {
291+
pub fn process_monitor_nodes(&mut self, responses: &mut Vec<ExecutionResponseMessage>, update_thumbnails: bool) {
293292
// TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n))
294293
self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id)));
295-
294+
let mut updated_thumbnails = false;
296295
for monitor_node_path in &self.monitor_nodes {
297296
// Skip the inspect monitor node
298297
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 {
316315
// Graphic table: thumbnail
317316
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
318317
if update_thumbnails {
319-
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
318+
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses);
319+
responses.push(ExecutionResponseMessage::UpdateNodeGraphThumbnail(parent_network_node_id, io.output.clone().to_graphic()));
320+
updated_thumbnails = true;
320321
}
321322
}
322323
// Artboard table: thumbnail
323324
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
324325
if update_thumbnails {
325-
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
326+
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses);
327+
responses.push(ExecutionResponseMessage::UpdateNodeGraphThumbnail(parent_network_node_id, io.output.clone().to_graphic()));
328+
updated_thumbnails = true;
326329
}
327330
}
328331
// Vector table: vector modifications
@@ -337,18 +340,21 @@ impl NodeRuntime {
337340
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
338341
}
339342
}
343+
if updated_thumbnails {
344+
responses.push(ExecutionResponseMessage::SendGraph);
345+
}
340346
}
341347

342348
/// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
343-
fn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque<FrontendMessage>) {
349+
fn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut Vec<ExecutionResponseMessage>) {
344350
// Skip thumbnails if the layer is too complex (for performance)
345351
if graphic.render_complexity() > 1000 {
346352
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
347353
if old.is_none_or(|v| !v.is_empty()) {
348-
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
349-
id: parent_network_node_id,
350-
value: "<svg viewBox=\"0 0 10 10\"><title>Dense thumbnail omitted for performance</title><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>".to_string(),
351-
});
354+
responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail(
355+
parent_network_node_id,
356+
"<svg viewBox=\"0 0 10 10\"><title>Dense thumbnail omitted for performance</title><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>".to_string(),
357+
));
352358
}
353359
return;
354360
}
@@ -382,10 +388,7 @@ impl NodeRuntime {
382388
let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default();
383389

384390
if old_thumbnail_svg != &new_thumbnail_svg {
385-
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
386-
id: parent_network_node_id,
387-
value: new_thumbnail_svg.to_svg_string(),
388-
});
391+
responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail(parent_network_node_id, new_thumbnail_svg.to_svg_string()));
389392
*old_thumbnail_svg = new_thumbnail_svg;
390393
}
391394
}

node-graph/gcore/src/node_graph_overlay.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub mod ui_context;
2121
#[node_macro::node(skip_impl)]
2222
pub fn generate_nodes(_: impl Ctx, mut node_graph_overlay_data: NodeGraphOverlayData) -> Table<Graphic> {
2323
let mut nodes_and_wires = Table::new();
24-
let (layers, side_ports) = draw_layers(&node_graph_overlay_data.nodes_to_render);
24+
let (layers, side_ports) = draw_layers(&mut node_graph_overlay_data);
2525
nodes_and_wires.extend(layers);
2626

2727
let wires = draw_wires(&mut node_graph_overlay_data.nodes_to_render);

node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ use crate::{
88
consts::SOURCE_SANS_FONT_DATA,
99
node_graph_overlay::{
1010
consts::*,
11-
types::{FrontendGraphDataType, FrontendNodeToRender},
11+
types::{FrontendGraphDataType, FrontendNodeToRender, NodeGraphOverlayData},
1212
},
1313
table::{Table, TableRow},
1414
text::{self, TextAlign, TypesettingConfig},
1515
transform::ApplyTransform,
1616
vector::{
1717
Vector,
18+
style::{Fill, Stroke},
1819
style::{Fill, Stroke, StrokeAlign},
1920
},
2021
};
@@ -211,10 +212,10 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
211212
node_table
212213
}
213214

214-
pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<Graphic>) {
215+
pub fn draw_layers(nodes: &mut NodeGraphOverlayData) -> (Table<Graphic>, Table<Graphic>) {
215216
let mut layer_table = Table::new();
216217
let mut side_ports_table = Table::new();
217-
for node_to_render in nodes {
218+
for node_to_render in &nodes.nodes_to_render {
218219
if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() {
219220
// The layer position is the top left of the thumbnail
220221
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<FrontendNodeToRender>) -> (Table<Graphic>, Table<
360361
}
361362
let bottom_port = BezPath::from_svg("M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z").unwrap();
362363
let mut vector = Vector::from_bezpath(bottom_port);
363-
let mut bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() {
364+
let bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() {
364365
frontend_layer.bottom_input.data_type.data_color()
365366
} else {
366367
frontend_layer.bottom_input.data_type.data_color_dim()
@@ -457,10 +458,34 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
457458
inner_thumbnail_table.push(TableRow::new_from_element(vector));
458459
}
459460
}
460-
let mut thumbnail_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table));
461-
thumbnail_row.alpha_blending.clip = true;
462-
let graphic_table = Table::new_from_row(thumbnail_row);
463-
layer_table.push(TableRow::new_from_element(Graphic::Graphic(graphic_table)));
461+
let mut thumbnail_grid_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table));
462+
thumbnail_grid_row.alpha_blending.clip = true;
463+
let mut clipped_thumbnail_table = Table::new();
464+
clipped_thumbnail_table.push(thumbnail_grid_row);
465+
if let Some(thumbnail_graphic) = nodes.thumbnails.get_mut(&node_to_render.metadata.node_id) {
466+
let thumbnail_graphic = std::mem::take(thumbnail_graphic);
467+
let bbox = thumbnail_graphic.bounding_box(DAffine2::default(), false);
468+
if let RenderBoundingBox::Rectangle(rect) = bbox {
469+
let rect_size = rect[1] - rect[0];
470+
let target_size = DVec2::new(68., 44.);
471+
// uniform scale that fits in target box
472+
let scale_x = target_size.x / rect_size.x;
473+
let scale_y = target_size.y / rect_size.y;
474+
let scale = scale_x.min(scale_y);
475+
476+
let translation = rect[0] * -scale;
477+
let scaled_size = rect_size * scale;
478+
let offset_to_center = (target_size - scaled_size) / 2.;
479+
480+
let mut thumbnail_graphic_row = TableRow::new_from_element(thumbnail_graphic);
481+
thumbnail_graphic_row.transform = DAffine2::from_translation(layer_position + offset_to_center) * DAffine2::from_scale_angle_translation(DVec2::splat(scale), 0., translation);
482+
thumbnail_graphic_row.alpha_blending.clip = true;
483+
484+
clipped_thumbnail_table.push(thumbnail_graphic_row);
485+
}
486+
}
487+
488+
layer_table.push(TableRow::new_from_element(Graphic::Graphic(clipped_thumbnail_table)));
464489
}
465490
}
466491

node-graph/gcore/src/node_graph_overlay/types.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use glam::{DAffine2, DVec2};
22
use graphene_core_shaders::color::Color;
33
use kurbo::BezPath;
44

5-
use crate::{node_graph_overlay::consts::*, uuid::NodeId};
6-
use std::hash::{Hash, Hasher};
5+
use crate::{Graphic, node_graph_overlay::consts::*, uuid::NodeId};
6+
use std::{
7+
collections::HashMap,
8+
hash::{Hash, Hasher},
9+
};
710

811
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
912
pub struct NodeGraphTransform {
@@ -27,13 +30,28 @@ impl NodeGraphTransform {
2730
}
2831
}
2932

30-
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
33+
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
3134
pub struct NodeGraphOverlayData {
3235
pub nodes_to_render: Vec<FrontendNodeToRender>,
3336
pub open: bool,
3437
pub in_selected_network: bool,
3538
// Displays a dashed border around the node
3639
pub previewed_node: Option<NodeId>,
40+
pub thumbnails: HashMap<NodeId, Graphic>,
41+
}
42+
43+
impl Hash for NodeGraphOverlayData {
44+
fn hash<H: Hasher>(&self, state: &mut H) {
45+
self.nodes_to_render.hash(state);
46+
self.open.hash(state);
47+
self.in_selected_network.hash(state);
48+
self.previewed_node.hash(state);
49+
let mut entries: Vec<_> = self.thumbnails.iter().collect();
50+
entries.sort_by(|a, b| a.0.cmp(b.0));
51+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
52+
entries.hash(&mut hasher);
53+
hasher.finish();
54+
}
3755
}
3856

3957
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]

0 commit comments

Comments
 (0)