thumbnails

This commit is contained in:
Adam
2025-09-08 19:37:59 -07:00
parent a25a23e61e
commit 8caf9317a5
8 changed files with 182 additions and 43 deletions
@@ -7,6 +7,7 @@ use glam::IVec2;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_std::Graphic;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
#[impl_message(Message, DocumentMessage, NodeGraph)] #[impl_message(Message, DocumentMessage, NodeGraph)]
@@ -218,6 +219,10 @@ pub enum NodeGraphMessage {
UpdateImportsExports, UpdateImportsExports,
UpdateLayerPanel, UpdateLayerPanel,
UpdateNewNodeGraph, UpdateNewNodeGraph,
UpdateThumbnail {
node_id: NodeId,
graphic: Graphic,
},
UpdateTypes { UpdateTypes {
#[serde(skip)] #[serde(skip)]
resolved_types: ResolvedDocumentNodeTypesDelta, resolved_types: ResolvedDocumentNodeTypesDelta,
@@ -92,6 +92,8 @@ pub struct NodeGraphMessageHandler {
reordering_export: Option<usize>, reordering_export: Option<usize>,
/// The end index of the moved connector /// The end index of the moved connector
end_index: Option<usize>, end_index: Option<usize>,
// The rendered string for each thumbnail
pub thumbnails: HashMap<NodeId, Graphic>,
} }
/// 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. /// 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
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
NodeGraphMessage::UpdateThumbnail { node_id, graphic } => {
self.thumbnails.insert(node_id, graphic);
}
NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {
network_interface.resolved_types.update(resolved_types); network_interface.resolved_types.update(resolved_types);
self.node_graph_errors = node_graph_errors; self.node_graph_errors = node_graph_errors;
@@ -2559,6 +2564,7 @@ impl Default for NodeGraphMessageHandler {
reordering_export: None, reordering_export: None,
reordering_import: None, reordering_import: None,
end_index: None, end_index: None,
thumbnails: HashMap::new(),
} }
} }
} }
+24 -13
View File
@@ -29,10 +29,16 @@ pub struct ExecutionRequest {
pub struct ExecutionResponse { pub struct ExecutionResponse {
execution_id: u64, execution_id: u64,
result: Result<TaggedValue, String>, result: Result<TaggedValue, String>,
responses: VecDeque<FrontendMessage>, execution_responses: Vec<ExecutionResponseMessage>,
vector_modify: HashMap<NodeId, Vector>, vector_modify: HashMap<NodeId, Vector>,
}
pub enum ExecutionResponseMessage {
/// The resulting value from the temporary inspected during execution /// The resulting value from the temporary inspected during execution
inspect_result: Option<InspectResult>, InspectResult(Option<InspectResult>),
UpdateNodeGraphThumbnail(NodeId, Graphic),
UpdateFrontendThumbnail(NodeId, String),
SendGraph,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
@@ -260,11 +266,24 @@ impl NodeGraphExecutor {
let ExecutionResponse { let ExecutionResponse {
execution_id, execution_id,
result, result,
responses: existing_responses, execution_responses,
vector_modify, vector_modify,
inspect_result,
} = execution_response; } = 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); responses.add(OverlaysMessage::Draw);
let node_graph_output = match result { 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); document.network_interface.update_vector_modify(vector_modify);
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?; let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
@@ -291,13 +309,6 @@ impl NodeGraphExecutor {
execution_id, execution_id,
document_id: execution_context.document_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) => { NodeGraphUpdate::CompilationResponse(execution_response) => {
let CompilationResponse { node_graph_errors, result } = execution_response; let CompilationResponse { node_graph_errors, result } = execution_response;
+21 -18
View File
@@ -212,14 +212,14 @@ impl NodeRuntime {
} }
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => { GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
let result = self.execute_network(render_config).await; 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 // 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; self.update_thumbnails = false;
// Resolve the result from the inspection by accessing the monitor node // Resolve the result from the inspection by accessing the monitor node
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor)); 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 { let texture = if let Ok(TaggedValue::RenderOutput(RenderOutput {
data: RenderOutputType::Texture(texture), data: RenderOutputType::Texture(texture),
.. ..
@@ -233,9 +233,8 @@ impl NodeRuntime {
self.sender.send_execution_response(ExecutionResponse { self.sender.send_execution_response(ExecutionResponse {
execution_id, execution_id,
result, result,
responses, execution_responses,
vector_modify: self.vector_modify.clone(), vector_modify: self.vector_modify.clone(),
inspect_result,
}); });
return texture; return texture;
} }
@@ -289,10 +288,10 @@ impl NodeRuntime {
} }
/// Updates state data /// Updates state data
pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<FrontendMessage>, update_thumbnails: bool) { pub fn process_monitor_nodes(&mut self, responses: &mut Vec<ExecutionResponseMessage>, 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)) // 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))); 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 { for monitor_node_path in &self.monitor_nodes {
// Skip the inspect monitor node // Skip the inspect monitor node
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.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 // Graphic table: thumbnail
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
if update_thumbnails { 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 // Artboard table: thumbnail
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() { else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
if update_thumbnails { 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 // Vector table: vector modifications
@@ -337,18 +340,21 @@ impl NodeRuntime {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}"); 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. /// 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>) { fn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut Vec<ExecutionResponseMessage>) {
// Skip thumbnails if the layer is too complex (for performance) // Skip thumbnails if the layer is too complex (for performance)
if graphic.render_complexity() > 1000 { if graphic.render_complexity() > 1000 {
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new()); let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
if old.is_none_or(|v| !v.is_empty()) { if old.is_none_or(|v| !v.is_empty()) {
responses.push_back(FrontendMessage::UpdateNodeThumbnail { responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail(
id: parent_network_node_id, parent_network_node_id,
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(), "<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(),
}); ));
} }
return; return;
} }
@@ -382,10 +388,7 @@ impl NodeRuntime {
let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default(); let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default();
if old_thumbnail_svg != &new_thumbnail_svg { if old_thumbnail_svg != &new_thumbnail_svg {
responses.push_back(FrontendMessage::UpdateNodeThumbnail { responses.push(ExecutionResponseMessage::UpdateFrontendThumbnail(parent_network_node_id, new_thumbnail_svg.to_svg_string()));
id: parent_network_node_id,
value: new_thumbnail_svg.to_svg_string(),
});
*old_thumbnail_svg = new_thumbnail_svg; *old_thumbnail_svg = new_thumbnail_svg;
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ pub mod ui_context;
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
pub fn generate_nodes(_: impl Ctx, mut node_graph_overlay_data: NodeGraphOverlayData) -> Table<Graphic> { pub fn generate_nodes(_: impl Ctx, mut node_graph_overlay_data: NodeGraphOverlayData) -> Table<Graphic> {
let mut nodes_and_wires = Table::new(); 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); nodes_and_wires.extend(layers);
let wires = draw_wires(&mut node_graph_overlay_data.nodes_to_render); let wires = draw_wires(&mut node_graph_overlay_data.nodes_to_render);
@@ -8,13 +8,14 @@ use crate::{
consts::SOURCE_SANS_FONT_DATA, consts::SOURCE_SANS_FONT_DATA,
node_graph_overlay::{ node_graph_overlay::{
consts::*, consts::*,
types::{FrontendGraphDataType, FrontendNodeToRender}, types::{FrontendGraphDataType, FrontendNodeToRender, NodeGraphOverlayData},
}, },
table::{Table, TableRow}, table::{Table, TableRow},
text::{self, TextAlign, TypesettingConfig}, text::{self, TextAlign, TypesettingConfig},
transform::ApplyTransform, transform::ApplyTransform,
vector::{ vector::{
Vector, Vector,
style::{Fill, Stroke},
style::{Fill, Stroke, StrokeAlign}, style::{Fill, Stroke, StrokeAlign},
}, },
}; };
@@ -211,10 +212,10 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
node_table node_table
} }
pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<Graphic>) { pub fn draw_layers(nodes: &mut NodeGraphOverlayData) -> (Table<Graphic>, Table<Graphic>) {
let mut layer_table = Table::new(); let mut layer_table = Table::new();
let mut side_ports_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() { if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() {
// The layer position is the top left of the thumbnail // 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); 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<
} }
let bottom_port = BezPath::from_svg("M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z").unwrap(); 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 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() frontend_layer.bottom_input.data_type.data_color()
} else { } else {
frontend_layer.bottom_input.data_type.data_color_dim() frontend_layer.bottom_input.data_type.data_color_dim()
@@ -457,10 +458,34 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
inner_thumbnail_table.push(TableRow::new_from_element(vector)); inner_thumbnail_table.push(TableRow::new_from_element(vector));
} }
} }
let mut thumbnail_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table)); let mut thumbnail_grid_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table));
thumbnail_row.alpha_blending.clip = true; thumbnail_grid_row.alpha_blending.clip = true;
let graphic_table = Table::new_from_row(thumbnail_row); let mut clipped_thumbnail_table = Table::new();
layer_table.push(TableRow::new_from_element(Graphic::Graphic(graphic_table))); 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)));
} }
} }
@@ -2,8 +2,11 @@ use glam::{DAffine2, DVec2};
use graphene_core_shaders::color::Color; use graphene_core_shaders::color::Color;
use kurbo::BezPath; use kurbo::BezPath;
use crate::{node_graph_overlay::consts::*, uuid::NodeId}; use crate::{Graphic, node_graph_overlay::consts::*, uuid::NodeId};
use std::hash::{Hash, Hasher}; use std::{
collections::HashMap,
hash::{Hash, Hasher},
};
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct NodeGraphTransform { 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 struct NodeGraphOverlayData {
pub nodes_to_render: Vec<FrontendNodeToRender>, pub nodes_to_render: Vec<FrontendNodeToRender>,
pub open: bool, pub open: bool,
pub in_selected_network: bool, pub in_selected_network: bool,
// Displays a dashed border around the node // Displays a dashed border around the node
pub previewed_node: Option<NodeId>, pub previewed_node: Option<NodeId>,
pub thumbnails: HashMap<NodeId, Graphic>,
}
impl Hash for NodeGraphOverlayData {
fn hash<H: Hasher>(&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)] #[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
+71
View File
@@ -9,6 +9,7 @@ use graphene_core::color::Color;
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
use graphene_core::gradient::GradientType; use graphene_core::gradient::GradientType;
use graphene_core::math::quad::Quad; 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::BitmapMut;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster_types::{CPU, GPU, Raster}; 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::vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, ViewMode};
use graphene_core::{Artboard, Graphic}; use graphene_core::{Artboard, Graphic};
use kurbo::Affine; use kurbo::Affine;
use kurbo::Rect;
use kurbo::Shape;
use num_traits::Zero; use num_traits::Zero;
use skrifa::MetadataProvider; use skrifa::MetadataProvider;
use skrifa::attribute::Style; use skrifa::attribute::Style;
@@ -247,6 +250,8 @@ pub trait Render: BoundingBox + RenderComplexity {
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams); 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. /// 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<ClickTarget>) {} fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
@@ -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<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
match self { 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<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
@@ -505,6 +538,20 @@ impl Render for Table<Artboard> {
} }
} }
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<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for row in self.iter() { for row in self.iter() {
row.element.collect_metadata(metadata, footprint, *row.source_node_id); row.element.collect_metadata(metadata, footprint, *row.source_node_id);
@@ -643,6 +690,10 @@ impl Render for Table<Graphic> {
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::Graphic(self)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for row in self.iter() { for row in self.iter() {
if let Some(element_id) = row.source_node_id { if let Some(element_id) = row.source_node_id {
@@ -1141,6 +1192,10 @@ impl Render for Table<Vector> {
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::Vector(self)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for row in self.iter() { for row in self.iter() {
let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width); let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
@@ -1318,6 +1373,10 @@ impl Render for Table<Raster<CPU>> {
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::RasterCPU(self)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
click_targets.push(ClickTarget::new_with_subpath(subpath, 0.)); click_targets.push(ClickTarget::new_with_subpath(subpath, 0.));
@@ -1364,6 +1423,10 @@ impl Render for Table<Raster<GPU>> {
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::RasterGPU(self)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let Some(element_id) = element_id else { return }; let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
@@ -1444,6 +1507,10 @@ impl Render for Table<Color> {
} }
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::Color(self)
}
} }
impl Render for Table<GradientStops> { impl Render for Table<GradientStops> {
@@ -1544,6 +1611,10 @@ impl Render for Table<GradientStops> {
} }
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::Gradient(self)
}
} }
impl Render for Table<Typography> { impl Render for Table<Typography> {