Add Table<Color> as a graphical type (#3033)

* Reduce code duplication in bounding box impls on Table

* Working Table<Color> rendering in the graph

* Implement color and fix other rendering with Vello and polish
This commit is contained in:
Keavon Chambers
2025-08-10 01:34:33 -07:00
committed by GitHub
parent 81abfe147a
commit 2f4aef34e5
24 changed files with 462 additions and 198 deletions

View File

@@ -8,11 +8,13 @@ use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::bounds::RenderBoundingBox;
use graphene_std::memo::IORecord;
use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache;
use graphene_std::transform::RenderQuality;
use graphene_std::vector::Vector;
use graphene_std::vector::style::ViewMode;
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
@@ -202,8 +204,6 @@ impl NodeRuntime {
});
}
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
let transform = render_config.viewport.transform;
let result = self.execute_network(render_config).await;
let mut responses = VecDeque::new();
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
@@ -227,7 +227,7 @@ impl NodeRuntime {
execution_id,
result,
responses,
transform,
footprint: render_config.viewport,
vector_modify: self.vector_modify.clone(),
inspect_result,
});
@@ -292,51 +292,49 @@ impl NodeRuntime {
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
continue;
}
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
warn!("Monitor node has invalid node id");
continue;
};
// Extract the monitor node's stored `Graphic` data.
// Extract the monitor node's stored `Graphic` data
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
#[cfg(debug_assertions)]
warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err());
continue;
};
// Graphic table: thumbnail
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
if update_thumbnails {
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
}
}
// Artboard table: thumbnail
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
if update_thumbnails {
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
}
}
// Vector table: vector modifications
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
// Insert the vector modify
let default = TableRow::default();
self.vector_modify
.insert(parent_network_node_id, record.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
} else {
.insert(parent_network_node_id, io.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
}
// Other
else {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
}
}
}
// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic(
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
parent_network_node_id: NodeId,
graphic: &impl Render,
responses: &mut VecDeque<FrontendMessage>,
update_thumbnails: bool,
) {
// RENDER THUMBNAIL
if !update_thumbnails {
return;
}
/// 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<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque<FrontendMessage>) {
// 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());
@@ -349,12 +347,21 @@ impl NodeRuntime {
return;
}
let bounds = graphic.bounding_box(DAffine2::IDENTITY, true);
let bounds = match graphic.bounding_box(DAffine2::IDENTITY, true) {
RenderBoundingBox::None => return,
RenderBoundingBox::Infinite => [DVec2::ZERO, DVec2::new(300., 200.)],
RenderBoundingBox::Rectangle(bounds) => bounds,
};
let footprint = Footprint {
transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)),
resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32),
quality: RenderQuality::Full,
};
// Render the thumbnail from a `Graphic` into an SVG string
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: bounds,
footprint,
thumbnail: true,
hide_artboards: false,
for_export: false,
@@ -365,8 +372,7 @@ impl NodeRuntime {
graphic.render_svg(&mut render, &render_params);
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max);
render.format_svg(bounds[0], bounds[1]);
// UPDATE FRONTEND THUMBNAIL