mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
@@ -8,18 +8,17 @@ use std::borrow::Cow;
|
||||
pub enum FrontendGraphDataType {
|
||||
#[default]
|
||||
General,
|
||||
Number,
|
||||
Artboard,
|
||||
Graphic,
|
||||
Raster,
|
||||
Vector,
|
||||
Number,
|
||||
Graphic,
|
||||
Artboard,
|
||||
Color,
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::U32(_)
|
||||
| TaggedValue::U64(_)
|
||||
| TaggedValue::F64(_)
|
||||
@@ -28,8 +27,11 @@ impl FrontendGraphDataType {
|
||||
| TaggedValue::VecF64(_)
|
||||
| TaggedValue::VecDVec2(_)
|
||||
| TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Artboard(_) => Self::Artboard,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::ColorTable(_) | TaggedValue::Color(_) | TaggedValue::OptionalColor(_) => Self::Color,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ impl TableRowLayout for Graphic {
|
||||
Self::Vector(vector) => vector.identifier(),
|
||||
Self::RasterCPU(_) => "Raster (on CPU)".to_string(),
|
||||
Self::RasterGPU(_) => "Raster (on GPU)".to_string(),
|
||||
Self::Color(_) => "Color".to_string(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
@@ -170,6 +171,12 @@ impl TableRowLayout for Graphic {
|
||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterCPU(_) => label("Raster is not supported"),
|
||||
Self::RasterGPU(_) => label("Raster is not supported"),
|
||||
Self::Color(color) => {
|
||||
let rows = vec![vec![
|
||||
TextLabel::new(format!("Colors:\n{}", color.iter().map(|color| color.element.to_rgba_hex_srgb()).collect::<Vec<_>>().join("\n"))).widget_holder(),
|
||||
]];
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use graphene_std::text::FontCache;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use graphene_std::wasm_application_io::RenderOutputType;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||
|
||||
mod runtime_io;
|
||||
@@ -33,7 +34,7 @@ pub struct ExecutionResponse {
|
||||
execution_id: u64,
|
||||
result: Result<TaggedValue, String>,
|
||||
responses: VecDeque<FrontendMessage>,
|
||||
transform: DAffine2,
|
||||
footprint: Footprint,
|
||||
vector_modify: HashMap<NodeId, Vector>,
|
||||
/// The resulting value from the temporary inspected during execution
|
||||
inspect_result: Option<InspectResult>,
|
||||
@@ -223,7 +224,7 @@ impl NodeGraphExecutor {
|
||||
|
||||
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
let TaggedValue::RenderOutput(RenderOutput {
|
||||
data: graphene_std::wasm_application_io::RenderOutputType::Svg { svg, .. },
|
||||
data: RenderOutputType::Svg { svg, .. },
|
||||
..
|
||||
}) = node_graph_output
|
||||
else {
|
||||
@@ -263,7 +264,7 @@ impl NodeGraphExecutor {
|
||||
execution_id,
|
||||
result,
|
||||
responses: existing_responses,
|
||||
transform,
|
||||
footprint,
|
||||
vector_modify,
|
||||
inspect_result,
|
||||
} = execution_response;
|
||||
@@ -286,9 +287,9 @@ impl NodeGraphExecutor {
|
||||
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
if let Some(export_config) = execution_context.export_config {
|
||||
// Special handling for exporting the artwork
|
||||
self.export(node_graph_output, export_config, responses)?
|
||||
self.export(node_graph_output, export_config, responses)?;
|
||||
} else {
|
||||
self.process_node_graph_output(node_graph_output, transform, responses)?
|
||||
self.process_node_graph_output(node_graph_output, footprint, responses)?;
|
||||
}
|
||||
responses.add_front(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
|
||||
|
||||
@@ -332,12 +333,12 @@ impl NodeGraphExecutor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn debug_render(render_object: impl Render, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
fn debug_render(render_object: impl Render, footprint: Footprint, responses: &mut VecDeque<Message>) {
|
||||
// Setup rendering
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams {
|
||||
view_mode: ViewMode::Normal,
|
||||
culling_bounds: None,
|
||||
footprint,
|
||||
thumbnail: false,
|
||||
hide_artboards: false,
|
||||
for_export: false,
|
||||
@@ -349,24 +350,25 @@ impl NodeGraphExecutor {
|
||||
render_object.render_svg(&mut render, &render_params);
|
||||
|
||||
// Concatenate the defs and the SVG into one string
|
||||
render.wrap_with_transform(transform, None);
|
||||
render.wrap_with_transform(footprint.transform, None);
|
||||
let svg = render.svg.to_svg_string();
|
||||
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
}
|
||||
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, footprint: Footprint, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
let mut render_output_metadata = RenderMetadata::default();
|
||||
|
||||
match node_graph_output {
|
||||
TaggedValue::RenderOutput(render_output) => {
|
||||
match render_output.data {
|
||||
graphene_std::wasm_application_io::RenderOutputType::Svg { svg, image_data } => {
|
||||
RenderOutputType::Svg { svg, image_data } => {
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateImageData { image_data });
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
}
|
||||
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
|
||||
RenderOutputType::CanvasFrame(frame) => {
|
||||
let matrix = format_transform_matrix(frame.transform);
|
||||
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
|
||||
let svg = format!(
|
||||
@@ -375,29 +377,23 @@ impl NodeGraphExecutor {
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
}
|
||||
graphene_std::wasm_application_io::RenderOutputType::Texture { .. } => {}
|
||||
_ => {
|
||||
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
|
||||
}
|
||||
RenderOutputType::Texture { .. } => {}
|
||||
_ => return Err(format!("Invalid node graph output type: {:#?}", render_output.data)),
|
||||
}
|
||||
|
||||
render_output_metadata = render_output.metadata;
|
||||
}
|
||||
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Vector(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Graphic(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Raster(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
_ => {
|
||||
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
||||
}
|
||||
TaggedValue::Bool(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
TaggedValue::F64(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
TaggedValue::String(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
TaggedValue::Palette(render_object) => Self::debug_render(render_object, footprint, responses),
|
||||
_ => return Err(format!("Invalid node graph output type: {node_graph_output:#?}")),
|
||||
};
|
||||
|
||||
let graphene_std::renderer::RenderMetadata {
|
||||
upstream_footprints: footprints,
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_element_source_id,
|
||||
click_targets,
|
||||
@@ -406,7 +402,7 @@ impl NodeGraphExecutor {
|
||||
|
||||
// Run these update state messages immediately
|
||||
responses.add(DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints: footprints,
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_element_source_id,
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user