Improve previewing node data (#1446)

* Improve preview

* Improve contrast

* Restructure in order to duplicate code

* Code review nits

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-11-04 09:52:26 +00:00 committed by GitHub
parent c823016316
commit e0ac073805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 326 additions and 202 deletions

View File

@ -60,7 +60,7 @@ impl Default for Document {
inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))], inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))],
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork { implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
inputs: vec![3, 0], inputs: vec![3, 0],
outputs: vec![NodeOutput::new(4, 0)], outputs: vec![NodeOutput::new(3, 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
name: "EditorApi".to_string(), name: "EditorApi".to_string(),
@ -82,16 +82,14 @@ impl Default for Document {
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default() ..Default::default()
}, },
DocumentNode {
name: "Conversion".to_string(),
inputs: vec![NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, GraphicGroup>")),
..Default::default()
},
DocumentNode { DocumentNode {
name: "RenderNode".to_string(), name: "RenderNode".to_string(),
inputs: vec![NodeInput::node(0, 0), NodeInput::node(3, 0), NodeInput::node(2, 0)], inputs: vec![
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")), NodeInput::node(0, 0),
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T)))),
NodeInput::node(2, 0),
],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
..Default::default() ..Default::default()
}, },
] ]

View File

@ -290,7 +290,7 @@ impl core::fmt::Display for LayerNodeIdentifier {
} }
impl LayerNodeIdentifier { impl LayerNodeIdentifier {
const ROOT: Self = LayerNodeIdentifier::new_unchecked(0); pub const ROOT: Self = LayerNodeIdentifier::new_unchecked(0);
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node /// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
pub const fn new_unchecked(node_id: NodeId) -> Self { pub const fn new_unchecked(node_id: NodeId) -> Self {

View File

@ -94,7 +94,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
FitViewportToSelection => { FitViewportToSelection => {
if let Some(bounds) = selection_bounds { if let Some(bounds) = selection_bounds {
@ -214,7 +214,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
} }
SetCanvasRotation { angle_radians } => { SetCanvasRotation { angle_radians } => {
self.tilt = angle_radians; self.tilt = angle_radians;
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@ -224,7 +224,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
TransformCanvasEnd { abort_transform } => { TransformCanvasEnd { abort_transform } => {
if abort_transform { if abort_transform {
@ -235,12 +235,12 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
} }
TransformOperation::Pan { pre_commit_pan, .. } => { TransformOperation::Pan { pre_commit_pan, .. } => {
self.pan = pre_commit_pan; self.pan = pre_commit_pan;
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
TransformOperation::Zoom { pre_commit_zoom, .. } => { TransformOperation::Zoom { pre_commit_zoom, .. } => {
self.zoom = pre_commit_zoom; self.zoom = pre_commit_zoom;
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
} }
} }
@ -264,7 +264,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
self.pan += transformed_delta; self.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed); responses.add(BroadcastEvent::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
TranslateCanvasBegin => { TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
@ -281,7 +281,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
self.pan += transformed_delta; self.pan += transformed_delta;
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
self.create_document_transform(responses); self.create_document_transform(ipp.viewport_bounds.center(), responses);
} }
WheelCanvasTranslate { use_y_as_x } => { WheelCanvasTranslate { use_y_as_x } => {
let delta = match use_y_as_x { let delta = match use_y_as_x {
@ -381,21 +381,21 @@ impl NavigationMessageHandler {
} }
} }
pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 { pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 {
// Try to avoid fractional coordinates to reduce anti aliasing. // Try to avoid fractional coordinates to reduce anti aliasing.
let scale = self.snapped_scale(); let scale = self.snapped_scale();
let rounded_pan = ((self.pan + offset) * scale).round() / scale - offset; let rounded_pan = ((self.pan + viewport_center) * scale).round() / scale - viewport_center;
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(offset); let offset_transform = DAffine2::from_translation(viewport_center);
let scale_transform = DAffine2::from_scale(DVec2::splat(scale)); let scale_transform = DAffine2::from_scale(DVec2::splat(scale));
let angle_transform = DAffine2::from_angle(self.snapped_angle()); let angle_transform = DAffine2::from_angle(self.snapped_angle());
let translation_transform = DAffine2::from_translation(rounded_pan); let translation_transform = DAffine2::from_translation(rounded_pan);
scale_transform * offset_transform * angle_transform * translation_transform scale_transform * offset_transform * angle_transform * offset_transform.inverse() * translation_transform
} }
fn create_document_transform(&self, responses: &mut VecDeque<Message>) { fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) {
let transform = self.calculate_offset_transform(DVec2::ZERO); let transform = self.calculate_offset_transform(viewport_center);
responses.add(DocumentMessage::UpdateDocumentTransform { transform }); responses.add(DocumentMessage::UpdateDocumentTransform { transform });
} }

View File

@ -120,7 +120,11 @@ impl<'a> ModifyInputsContext<'a> {
// Update the document metadata structure // Update the document metadata structure
if let Some(new_id) = new_id { if let Some(new_id) = new_id {
let parent = LayerNodeIdentifier::new(output_node_id, self.network); let parent = if self.network.nodes.get(&output_node_id).is_some_and(|node| node.name == "Layer") {
LayerNodeIdentifier::new(output_node_id, self.network)
} else {
LayerNodeIdentifier::ROOT
};
let new_child = LayerNodeIdentifier::new(new_id, self.network); let new_child = LayerNodeIdentifier::new(new_id, self.network);
parent.push_front_child(self.document_metadata, new_child); parent.push_front_child(self.document_metadata, new_child);
self.responses.add(DocumentMessage::DocumentStructureChanged); self.responses.add(DocumentMessage::DocumentStructureChanged);
@ -562,25 +566,25 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
} }
GraphOperationMessage::NewArtboard { id, artboard } => { GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
modify_inputs.insert_artboard(artboard, layer); modify_inputs.insert_artboard(artboard, layer);
} }
} }
GraphOperationMessage::NewBitmapLayer { id, image_frame } => { GraphOperationMessage::NewBitmapLayer { id, image_frame } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
modify_inputs.insert_image_data(image_frame, layer); modify_inputs.insert_image_data(image_frame, layer);
} }
} }
GraphOperationMessage::NewVectorLayer { id, subpaths } => { GraphOperationMessage::NewVectorLayer { id, subpaths } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
modify_inputs.insert_vector_data(subpaths, layer); modify_inputs.insert_vector_data(subpaths, layer);
} }
} }
GraphOperationMessage::NewTextLayer { id, text, font, size } => { GraphOperationMessage::NewTextLayer { id, text, font, size } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
modify_inputs.insert_text(text, font, size, layer); modify_inputs.insert_text(text, font, size, layer);
} }
} }

View File

@ -475,7 +475,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType { DocumentNodeType {
name: "End Scope", name: "End Scope",
category: "Ignore", category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_>"), identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"),
inputs: vec![ inputs: vec![
DocumentInputType { DocumentInputType {
name: "Scope", name: "Scope",

View File

@ -546,7 +546,7 @@ fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Option<Color>
move |_: &IconButton| { move |_: &IconButton| {
let mut new_positions = positions.clone(); let mut new_positions = positions.clone();
// Blend linearly between the two colours. // Blend linearly between the two colors.
let get_color = |index: usize| match (new_positions[index].1, new_positions.get(index + 1).and_then(|x| x.1)) { let get_color = |index: usize| match (new_positions[index].1, new_positions.get(index + 1).and_then(|x| x.1)) {
(Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)), (Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)),
(Some(v), _) | (_, Some(v)) => Some(v), (Some(v), _) | (_, Some(v)) => Some(v),

View File

@ -9,13 +9,13 @@ use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscrimina
use document_legacy::{LayerId, Operation}; use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graph_craft::graphene_compiler::Compiler; use graph_craft::graphene_compiler::Compiler;
use graph_craft::imaginate_input::ImaginatePreferences; use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::{concrete, Type}; use graph_craft::{concrete, Type};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::raster::{Image, ImageFrame}; use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList}; use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache; use graphene_core::text::FontCache;
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::{Footprint, Transform};
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
@ -82,6 +82,7 @@ pub(crate) struct GenerationResponse {
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>,
new_upstream_transforms: HashMap<NodeId, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>,
transform: DAffine2,
} }
enum NodeGraphUpdate { enum NodeGraphUpdate {
@ -160,6 +161,7 @@ impl NodeRuntime {
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(), new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(), new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(),
new_upstream_transforms: self.upstream_transforms.clone(), new_upstream_transforms: self.upstream_transforms.clone(),
transform,
}; };
self.sender.send_generation_response(response); self.sender.send_generation_response(response);
} }
@ -167,7 +169,7 @@ impl NodeRuntime {
} }
} }
async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, MonitorNodes) { async fn execute_network<'a>(&'a mut self, path: &[LayerId], mut graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, MonitorNodes) {
if self.wasm_io.is_none() { if self.wasm_io.is_none() {
self.wasm_io = Some(WasmApplicationIo::new().await); self.wasm_io = Some(WasmApplicationIo::new().await);
} }
@ -512,6 +514,7 @@ impl NodeGraphExecutor {
new_click_targets, new_click_targets,
new_transforms, new_transforms,
new_upstream_transforms, new_upstream_transforms,
transform,
}) => { }) => {
self.thumbnails = new_thumbnails; self.thumbnails = new_thumbnails;
document.metadata.update_transforms(new_transforms, new_upstream_transforms); document.metadata.update_transforms(new_transforms, new_upstream_transforms);
@ -519,7 +522,7 @@ impl NodeGraphExecutor {
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {e:?}"))?; let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {e:?}"))?;
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
responses.extend(updates); responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?; self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?;
responses.add(DocumentMessage::LayerChanged { responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path, affected_layer_path: execution_context.layer_path,
}); });
@ -537,56 +540,40 @@ impl NodeGraphExecutor {
Ok(()) Ok(())
} }
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> { fn render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
use graphene_core::renderer::{ImageRenderMode, RenderParams, SvgRender};
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, None, false);
// Render SVG
render_object.render_svg(&mut render, &render_params);
// Concatenate the defs and the SVG into one string
render.wrap_with_transform(transform);
let svg = render.svg.to_string();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
}
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty())); self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty()));
match node_graph_output { match node_graph_output {
TaggedValue::VectorData(vector_data) => {
// Update the cached vector data on the layer
let transform = vector_data.transform.to_cols_array();
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
}
TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => { TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => {
let transform = transform.to_cols_array(); let transform = transform.to_cols_array();
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform }); responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
responses.add(Operation::SetSurface { path: layer_path, surface_id }); responses.add(Operation::SetSurface { path: layer_path, surface_id });
} }
TaggedValue::ImageFrame(ImageFrame { image, transform }) => {
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
// If no image was generated, clear the frame
if image.width == 0 || image.height == 0 {
responses.add(DocumentMessage::FrameClear);
// Update the transform based on the graph output
if let Some(transform) = transform {
responses.add(Operation::SetLayerTransform { path: layer_path, transform });
}
} else {
// Update the image data
let image_data = vec![Self::to_frontend_image_data(image, transform, &layer_path, None, None)?];
responses.add(FrontendMessage::UpdateImageData { document_id, image_data });
}
}
TaggedValue::Artboard(artboard) => {
warn!("Rendered graph produced artboard (which is not currently rendered): {artboard:#?}");
return Err("Artboard (see console)".to_string());
}
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => { TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
// Send to frontend // Send to frontend
//log::debug!("svg: {svg}");
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
//return Err("Graphic group (see console)".to_string());
} }
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => { TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
// Send to frontend // Send to frontend
//log::debug!("svg: {svg}");
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
let matrix = frame let matrix = frame
.transform .transform
.to_cols_array() .to_cols_array()
@ -600,30 +587,14 @@ impl NodeGraphExecutor {
1920, 1080, matrix, frame.surface_id.0 1920, 1080, matrix, frame.surface_id.0
); );
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
//return Err("Graphic group (see console)".to_string());
}
TaggedValue::GraphicGroup(graphic_group) => {
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::BlobUrl, None, false);
// Render svg
graphic_group.render_svg(&mut render, &render_params);
// Conctenate the defs and the svg into one string
let mut svg = "<defs>".to_string();
svg.push_str(&render.svg_defs);
svg.push_str("</defs>");
use std::fmt::Write;
write!(svg, "{}", render.svg).unwrap();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
responses.add(DocumentMessage::RenderScrollbars);
} }
TaggedValue::Bool(render_object) => Self::render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::render(render_object, transform, responses),
TaggedValue::F32(render_object) => Self::render(render_object, transform, responses),
TaggedValue::F64(render_object) => Self::render(render_object, transform, responses),
TaggedValue::OptionalColor(render_object) => Self::render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::render(render_object, transform, responses),
TaggedValue::ImageFrame(render_object) => Self::render(render_object, transform, responses),
_ => { _ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}")); return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
} }
@ -643,6 +614,6 @@ impl NodeGraphExecutor {
return; return;
} }
} }
warn!("Recieved blob url for invalid segment") warn!("Received blob url for invalid segment")
} }
} }

View File

@ -2,7 +2,7 @@
## Purpose of Nodes ## Purpose of Nodes
Graphite is an image editor which is centred around a node based editing workflow, which allows operations to be visually connected in a graph. This is flexible as it allows all operations to be viewed or modified at any time without losing original data. The node system has been designed to be as general as possible with all data types being representable and a broad selection of nodes for a variety of use cases being planned. Graphite is an image editor which is centered around a node based editing workflow, which allows operations to be visually connected in a graph. This is flexible as it allows all operations to be viewed or modified at any time without losing original data. The node system has been designed to be as general as possible with all data types being representable and a broad selection of nodes for a variety of use cases being planned.
## The Document Graph ## The Document Graph

View File

@ -204,6 +204,18 @@ impl<'a, T> AsRef<EditorApi<'a, T>> for EditorApi<'a, T> {
} }
} }
// Required for the EndLetNode
impl<'a, IO> From<EditorApi<'a, IO>> for Footprint {
fn from(value: EditorApi<'a, IO>) -> Self {
value.render_config.viewport
}
}
// Required for the EndLetNode
impl<'a, IO> From<EditorApi<'a, IO>> for () {
fn from(_value: EditorApi<'a, IO>) -> Self {}
}
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct ExtractImageFrame; pub struct ExtractImageFrame;

View File

@ -88,6 +88,15 @@ impl SvgRender {
self.svg.push("</svg>"); self.svg.push("</svg>");
} }
/// Wraps the SVG with `<svg><g transform="...">`, which allows for rotation
pub fn wrap_with_transform(&mut self, transform: DAffine2) {
let defs = &self.svg_defs;
let svg_header = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><defs>{defs}</defs><g transform="{}">"#, format_transform_matrix(transform));
self.svg.insert(0, svg_header.into());
self.svg.push("</g></svg>");
}
pub fn leaf_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) { pub fn leaf_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) {
self.indent(); self.indent();
self.svg.push("<"); self.svg.push("<");
@ -97,6 +106,11 @@ impl SvgRender {
self.svg.push("/>"); self.svg.push("/>");
} }
pub fn leaf_node(&mut self, content: impl Into<SvgSegment>) {
self.indent();
self.svg.push(content);
}
pub fn parent_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) { pub fn parent_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) {
let name = name.into(); let name = name.into();
self.indent(); self.indent();
@ -359,6 +373,55 @@ impl GraphicElementRendered for GraphicElementData {
} }
} }
/// Used to stop rust complaining about upstream traits adding display implementations to `Option<Color>`. This would not be an issue as we control that crate.
trait Primitive: core::fmt::Display {}
impl Primitive for String {}
impl Primitive for bool {}
impl Primitive for f32 {}
impl Primitive for f64 {}
fn text_attributes(attributes: &mut SvgRenderAttrs) {
attributes.push("fill", "white");
attributes.push("y", "30");
attributes.push("font-size", "30");
}
impl<T: Primitive> GraphicElementRendered for T {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
render.parent_tag("text", text_attributes, |render| render.leaf_node(format!("{self}")));
}
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None
}
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
}
impl GraphicElementRendered for Option<Color> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
let Some(color) = self else {
render.parent_tag("text", |_| {}, |render| render.leaf_node("Empty color"));
return;
};
render.leaf_tag("rect", |attributes| {
attributes.push("width", "100");
attributes.push("height", "100");
attributes.push("y", "40");
attributes.push("fill", format!("#{}", color.rgba_hex()));
});
let color_info = format!("{:?} #{} {:?}", color, color.rgba_hex(), color.to_rgba8_srgb());
render.parent_tag("text", text_attributes, |render| render.leaf_node(color_info))
}
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None
}
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
}
/// A segment of an svg string to allow for embedding blob urls /// A segment of an svg string to allow for embedding blob urls
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum SvgSegment { pub enum SvgSegment {

View File

@ -111,23 +111,24 @@ impl<T> LetNode<T> {
/// Caches the output of a given Node and acts as a proxy /// Caches the output of a given Node and acts as a proxy
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EndLetNode<Input> { pub struct EndLetNode<Input, Parameter> {
input: Input, input: Input,
paramenter: PhantomData<Parameter>,
} }
impl<'i, T: 'i, Input> Node<'i, T> for EndLetNode<Input> impl<'i, T: 'i, Parameter: 'i + From<T>, Input> Node<'i, T> for EndLetNode<Input, Parameter>
where where
Input: Node<'i, ()>, Input: Node<'i, Parameter>,
{ {
type Output = <Input>::Output; type Output = <Input>::Output;
fn eval(&'i self, _: T) -> Self::Output { fn eval(&'i self, t: T) -> Self::Output {
let result = self.input.eval(()); let result = self.input.eval(Parameter::from(t));
result result
} }
} }
impl<Input> EndLetNode<Input> { impl<Input, Parameter> EndLetNode<Input, Parameter> {
pub const fn new(input: Input) -> EndLetNode<Input> { pub const fn new(input: Input) -> EndLetNode<Input, Parameter> {
EndLetNode { input } EndLetNode { input, paramenter: PhantomData }
} }
} }

View File

@ -65,6 +65,8 @@ pub struct GenerateBrightnessContrastMapperNode<Brightness, Contrast> {
contrast: Contrast, contrast: Contrast,
} }
// TODO: Replace this node implementation with one that reuses the more generalized Curves adjustment node.
// TODO: It will be necessary to ensure the tests below are faithfully translated in a way that ensures identical results.
#[node_macro::node_fn(GenerateBrightnessContrastMapperNode)] #[node_macro::node_fn(GenerateBrightnessContrastMapperNode)]
fn brightness_contrast_node(_primary: (), brightness: f32, contrast: f32) -> BrightnessContrastMapperNode { fn brightness_contrast_node(_primary: (), brightness: f32, contrast: f32) -> BrightnessContrastMapperNode {
// Brightness LUT // Brightness LUT

View File

@ -127,12 +127,12 @@ impl Gradient {
// Compute the color of the inserted stop // Compute the color of the inserted stop
let get_color = |index: usize, time: f64| match (self.positions[index].1, self.positions.get(index + 1).and_then(|x| x.1)) { let get_color = |index: usize, time: f64| match (self.positions[index].1, self.positions.get(index + 1).and_then(|x| x.1)) {
// Lerp between the nearest colours if applicable // Lerp between the nearest colors if applicable
(Some(a), Some(b)) => a.lerp( (Some(a), Some(b)) => a.lerp(
b, b,
((time - self.positions[index].0) / self.positions.get(index + 1).map(|end| end.0 - self.positions[index].0).unwrap_or_default()) as f32, ((time - self.positions[index].0) / self.positions.get(index + 1).map(|end| end.0 - self.positions[index].0).unwrap_or_default()) as f32,
), ),
// Use the start or the end colour if applicable // Use the start or the end color if applicable
(Some(v), _) | (_, Some(v)) => v, (Some(v), _) | (_, Some(v)) => v,
_ => Color::WHITE, _ => Color::WHITE,
}; };

View File

@ -1019,18 +1019,18 @@ impl NodeNetwork {
// We filter out the newly inserted empty stack in case `resolve_empty_stacks` runs multiple times. // We filter out the newly inserted empty stack in case `resolve_empty_stacks` runs multiple times.
for node in self.nodes.values_mut().filter(|node| node.name != EMPTY_STACK) { for node in self.nodes.values_mut().filter(|node| node.name != EMPTY_STACK) {
for input in &mut node.inputs { for input in &mut node.inputs {
if matches!( if let NodeInput::Value {
input, tagged_value: TaggedValue::GraphicGroup(graphic_group),
NodeInput::Value {
tagged_value: TaggedValue::GraphicGroup(GraphicGroup::EMPTY),
.. ..
} } = input
) { {
if *graphic_group == GraphicGroup::EMPTY {
*input = NodeInput::node(new_id, 0); *input = NodeInput::node(new_id, 0);
used = true; used = true;
} }
} }
} }
}
// Only insert the node if necessary. // Only insert the node if necessary.
if used { if used {

View File

@ -141,7 +141,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
inner_network, inner_network,
DocumentNode { DocumentNode {
name: "End Scope".to_string(), name: "End Scope".to_string(),
implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_>"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"),
inputs: vec![NodeInput::node(0, 0), NodeInput::node(1, 0)], inputs: vec![NodeInput::node(0, 0), NodeInput::node(1, 0)],
..Default::default() ..Default::default()
}, },

View File

@ -42,7 +42,7 @@ graphene-core = { path = "../gcore", features = [
"alloc", "alloc",
], default-features = false } ], default-features = false }
dyn-any = { path = "../../libraries/dyn-any", features = ["derive"] } dyn-any = { path = "../../libraries/dyn-any", features = ["derive"] }
graph-craft = { path = "../graph-craft" } graph-craft = { path = "../graph-craft", features = ["serde"] }
vulkan-executor = { path = "../vulkan-executor", optional = true } vulkan-executor = { path = "../vulkan-executor", optional = true }
wgpu-executor = { path = "../wgpu-executor", optional = true, version = "0.1" } wgpu-executor = { path = "../wgpu-executor", optional = true, version = "0.1" }
gpu-executor = { path = "../gpu-executor", optional = true } gpu-executor = { path = "../gpu-executor", optional = true }

View File

@ -15,6 +15,7 @@ use graphene_core::{Color, GraphicGroup};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use js_sys::{Object, Reflect}; use js_sys::{Object, Reflect};
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
@ -288,35 +289,28 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
} }
pub use graph_craft::document::value::RenderOutput; pub use graph_craft::document::value::RenderOutput;
pub struct RenderNode<Data, Surface> { pub struct RenderNode<Data, Surface, Parameter> {
data: Data, data: Data,
surface_handle: Surface, surface_handle: Surface,
parameter: PhantomData<Parameter>,
} }
#[node_macro::node_fn(RenderNode)] fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput {
async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
editor: WasmEditorApi<'a>,
data: impl Node<'input, Footprint, Output = F>,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> RenderOutput {
let footprint = editor.render_config.viewport;
let data = self.data.eval(footprint).await;
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false);
let output_format = editor.render_config.export_format;
let resolution = footprint.resolution;
match output_format {
ExportFormat::Svg => {
data.render_svg(&mut render, &render_params); data.render_svg(&mut render, &render_params);
// TODO: reenable once we switch to full node graph render.wrap_with_transform(footprint.transform);
let min = footprint.transform.inverse().transform_point2((0., 0.).into());
let max = footprint.transform.inverse().transform_point2(resolution.as_dvec2());
render.format_svg(min, max);
RenderOutput::Svg(render.svg.to_string()) RenderOutput::Svg(render.svg.to_string())
} }
#[cfg(any(feature = "resvg", feature = "vello"))] #[cfg(any(feature = "resvg", feature = "vello"))]
ExportFormat::Canvas => { fn render_canvas(
data: impl GraphicElementRendered,
mut render: SvgRender,
render_params: RenderParams,
footprint: Footprint,
editor: WasmEditorApi<'_>,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> RenderOutput {
let resolution = footprint.resolution;
data.render_svg(&mut render, &render_params); data.render_svg(&mut render, &render_params);
// TODO: reenable once we switch to full node graph // TODO: reenable once we switch to full node graph
let min = footprint.transform.inverse().transform_point2((0., 0.).into()); let min = footprint.transform.inverse().transform_point2((0., 0.).into());
@ -359,6 +353,68 @@ async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
}; };
RenderOutput::CanvasFrame(frame.into()) RenderOutput::CanvasFrame(frame.into())
} }
_ => todo!("Non svg render output for {output_format:?}"),
// Render with the data node taking in Footprint.
impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, WasmEditorApi<'a>>
for RenderNode<Data, Surface, Footprint>
where
Data: Node<'input, Footprint, Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
#[inline]
fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output {
Box::pin(async move {
let footprint = editor.render_config.viewport;
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false);
let output_format = editor.render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint),
#[cfg(any(feature = "resvg", feature = "vello"))]
ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
})
}
}
// Render with the data node taking in ().
impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, WasmEditorApi<'a>>
for RenderNode<Data, Surface, ()>
where
Data: Node<'input, (), Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
#[inline]
fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output {
Box::pin(async move {
use graphene_core::renderer::ImageRenderMode;
let footprint = editor.render_config.viewport;
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::Base64, None, false);
let output_format = editor.render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint),
#[cfg(any(feature = "resvg", feature = "vello"))]
ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
})
}
}
#[automatically_derived]
impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
pub const fn new(data: Data, surface_handle: Surface) -> Self {
Self {
data,
surface_handle,
parameter: PhantomData,
}
} }
} }

View File

@ -11,7 +11,7 @@ use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::VectorData; use graphene_core::vector::VectorData;
use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame}; use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame};
use graphene_core::{concrete, generic, GraphicGroup}; use graphene_core::{concrete, generic, Artboard, GraphicGroup};
use graphene_core::{fn_type, raster::*}; use graphene_core::{fn_type, raster::*};
use graphene_core::{Cow, NodeIdentifier, Type}; use graphene_core::{Cow, NodeIdentifier, Type};
use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_core::{Node, NodeIO, NodeIOTypes};
@ -276,7 +276,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame<Color>, output: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: ImageFrame<Color>, output: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: VectorData, output: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: VectorData, output: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: GraphicGroup, output: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: GraphicGroup, output: GraphicGroup, params: []),
async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: graphene_core::Artboard, output: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: Artboard, output: GraphicGroup, params: []),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []), async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
@ -546,28 +546,35 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f32, f32, f32]), raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f32, f32, f32]),
register_node!(graphene_core::memo::LetNode<_>, input: Option<ImageFrame<Color>>, params: []), register_node!(graphene_core::memo::LetNode<_>, input: Option<ImageFrame<Color>>, params: []),
register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []), register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]), async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: VectorData, params: [VectorData]), async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: VectorData, params: [VectorData]),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: RenderOutput, params: [RenderOutput]), async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [RenderOutput]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [f32]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [f64]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [bool]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [String]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>]),
async_node!( async_node!(
graphene_core::memo::EndLetNode<_>, graphene_core::memo::EndLetNode<_, _>,
input: WasmEditorApi, input: WasmEditorApi,
output: graphene_core::GraphicGroup, output: GraphicGroup,
params: [graphene_core::GraphicGroup] params: [GraphicGroup]
), ),
async_node!( async_node!(
graphene_core::memo::EndLetNode<_>, graphene_core::memo::EndLetNode<_, _>,
input: WasmEditorApi, input: WasmEditorApi,
output: graphene_core::Artboard, output: Artboard,
params: [graphene_core::Artboard] params: [Artboard]
), ),
async_node!( async_node!(
graphene_core::memo::EndLetNode<_>, graphene_core::memo::EndLetNode<_, _>,
input: WasmEditorApi, input: WasmEditorApi,
output: WasmSurfaceHandleFrame, output: WasmSurfaceHandleFrame,
params: [WasmSurfaceHandleFrame] params: [WasmSurfaceHandleFrame]
), ),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: SurfaceFrame, params: [SurfaceFrame]), async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: SurfaceFrame, params: [SurfaceFrame]),
vec![ vec![
( (
NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"), NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"),
@ -639,7 +646,19 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]), register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]), register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]), async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => bool, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => f32, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => f64, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => String, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Option<Color>, () => Arc<WasmSurfaceHandle>]),
//register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]), //register_node!(graphene_core::transform::TranformNode<_, _, _, _, _, _>, input: , output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
vec![ vec![
( (
@ -740,34 +759,32 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
args.reverse(); args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<VectorData>(args.pop().expect("Not enough arguments provided to construct node"))); let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<VectorData>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node); let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = (core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>)> + '_> Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>> + '_>
}) })
}, },
{ {
let node = <graphene_core::transform::CullNode<_>>::new((graphene_std::any::PanicNode::<(), VectorData>::new())); let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::PanicNode::<(), VectorData>::new());
let params = vec![fn_type!((), VectorData)]; let params = vec![fn_type!((), VectorData)];
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params); let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
node_io.input = concrete!(<Footprint as StaticType>::Static); node_io.input = concrete!(<Footprint as StaticType>::Static);
node_io node_io
}, },
)], )],
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::Artboard]), register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [Artboard]),
vec![( vec![(
NodeIdentifier::new("graphene_core::transform::CullNode<_>"), NodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|args| { |args| {
Box::pin(async move { Box::pin(async move {
let mut args = args.clone(); let mut args = args.clone();
args.reverse(); args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<graphene_core::GraphicGroup>( let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<GraphicGroup>(args.pop().expect("Not enough arguments provided to construct node")));
args.pop().expect("Not enough arguments provided to construct node"),
));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node); let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = (core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>)> + '_> Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>> + '_>
}) })
}, },
{ {
let node = <graphene_core::transform::CullNode<_>>::new((graphene_std::any::PanicNode::<(), graphene_core::GraphicGroup>::new())); let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::PanicNode::<(), GraphicGroup>::new());
let params = vec![fn_type!((), graphene_core::GraphicGroup)]; let params = vec![fn_type!((), GraphicGroup)];
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params); let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
node_io.input = concrete!(<Footprint as StaticType>::Static); node_io.input = concrete!(<Footprint as StaticType>::Static);
node_io node_io
@ -792,12 +809,12 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []), register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
async_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, () => String, () => BlendMode, () => f32, () => bool, () => bool, () => bool, Footprint => graphene_core::GraphicGroup]), async_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElementData, () => String, () => BlendMode, () => f32, () => bool, () => bool, () => bool, Footprint => GraphicGroup]),
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []), register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []), register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []), register_node!(graphene_core::ToGraphicElementData, input: GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::Artboard, params: []), register_node!(graphene_core::ToGraphicElementData, input: Artboard, params: []),
register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]), register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]),
]; ];
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new(); let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() { for (id, c, types) in node_types.into_iter().flatten() {