diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 0ad28a26..ae6ede3f 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -60,7 +60,7 @@ impl Default for Document { inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))], implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork { inputs: vec![3, 0], - outputs: vec![NodeOutput::new(4, 0)], + outputs: vec![NodeOutput::new(3, 0)], nodes: [ DocumentNode { name: "EditorApi".to_string(), @@ -82,16 +82,14 @@ impl Default for Document { implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..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 { name: "RenderNode".to_string(), - inputs: vec![NodeInput::node(0, 0), NodeInput::node(3, 0), NodeInput::node(2, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")), + inputs: vec![ + 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() }, ] diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs index 46c826bd..f6b83377 100644 --- a/document-legacy/src/document_metadata.rs +++ b/document-legacy/src/document_metadata.rs @@ -290,7 +290,7 @@ impl core::fmt::Display for 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 pub const fn new_unchecked(node_id: NodeId) -> Self { diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 8cda504a..d5fd2e30 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -94,7 +94,7 @@ impl MessageHandler, &InputPre responses.add(BroadcastEvent::DocumentIsDirty); responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(responses); + self.create_document_transform(ipp.viewport_bounds.center(), responses); } FitViewportToSelection => { if let Some(bounds) = selection_bounds { @@ -214,7 +214,7 @@ impl MessageHandler, &InputPre } SetCanvasRotation { 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(PortfolioMessage::UpdateDocumentWidgets); } @@ -224,7 +224,7 @@ impl MessageHandler, &InputPre responses.add(BroadcastEvent::DocumentIsDirty); responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(responses); + self.create_document_transform(ipp.viewport_bounds.center(), responses); } TransformCanvasEnd { abort_transform } => { if abort_transform { @@ -235,12 +235,12 @@ impl MessageHandler, &InputPre } TransformOperation::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, .. } => { self.zoom = pre_commit_zoom; responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(responses); + self.create_document_transform(ipp.viewport_bounds.center(), responses); } } } @@ -264,7 +264,7 @@ impl MessageHandler, &InputPre self.pan += transformed_delta; responses.add(BroadcastEvent::CanvasTransformed); responses.add(BroadcastEvent::DocumentIsDirty); - self.create_document_transform(responses); + self.create_document_transform(ipp.viewport_bounds.center(), responses); } TranslateCanvasBegin => { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); @@ -281,7 +281,7 @@ impl MessageHandler, &InputPre self.pan += transformed_delta; responses.add(BroadcastEvent::DocumentIsDirty); - self.create_document_transform(responses); + self.create_document_transform(ipp.viewport_bounds.center(), responses); } WheelCanvasTranslate { 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. 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 - 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 angle_transform = DAffine2::from_angle(self.snapped_angle()); 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) { - let transform = self.calculate_offset_transform(DVec2::ZERO); + fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque) { + let transform = self.calculate_offset_transform(viewport_center); responses.add(DocumentMessage::UpdateDocumentTransform { transform }); } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs index bc6ed212..830950a2 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -120,7 +120,11 @@ impl<'a> ModifyInputsContext<'a> { // Update the document metadata structure 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); parent.push_front_child(self.document_metadata, new_child); self.responses.add(DocumentMessage::DocumentStructureChanged); @@ -562,25 +566,25 @@ impl MessageHandler { 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); } } GraphOperationMessage::NewBitmapLayer { id, image_frame } => { 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); } } GraphOperationMessage::NewVectorLayer { id, subpaths } => { 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); } } GraphOperationMessage::NewTextLayer { id, text, font, size } => { 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); } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 8ca330e3..53ddb462 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -475,7 +475,7 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "End Scope", category: "Ignore", - identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_>"), + identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"), inputs: vec![ DocumentInputType { name: "Scope", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 4b104ba2..958fe53c 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -546,7 +546,7 @@ fn gradient_row(row: &mut Vec, positions: &Vec<(f64, Option move |_: &IconButton| { 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)) { (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), diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 421acfea..2c41e920 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -9,13 +9,13 @@ use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscrimina use document_legacy::{LayerId, Operation}; 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::imaginate_input::ImaginatePreferences; use graph_craft::{concrete, Type}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; 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::transform::{Footprint, Transform}; use graphene_core::vector::style::ViewMode; @@ -82,6 +82,7 @@ pub(crate) struct GenerationResponse { new_click_targets: HashMap>, new_transforms: HashMap, new_upstream_transforms: HashMap, + transform: DAffine2, } 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_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(), new_upstream_transforms: self.upstream_transforms.clone(), + transform, }; 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, MonitorNodes) { + async fn execute_network<'a>(&'a mut self, path: &[LayerId], mut graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result, MonitorNodes) { if self.wasm_io.is_none() { self.wasm_io = Some(WasmApplicationIo::new().await); } @@ -512,6 +514,7 @@ impl NodeGraphExecutor { new_click_targets, new_transforms, new_upstream_transforms, + transform, }) => { self.thumbnails = new_thumbnails; 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 execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; 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 { affected_layer_path: execution_context.layer_path, }); @@ -537,56 +540,40 @@ impl NodeGraphExecutor { Ok(()) } - fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec, responses: &mut VecDeque, document_id: u64) -> Result<(), String> { + fn render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque) { + 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, transform: DAffine2, responses: &mut VecDeque, document_id: u64) -> Result<(), String> { self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty())); 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 }) => { let transform = transform.to_cols_array(); responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform }); 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)) => { // Send to frontend - //log::debug!("svg: {svg}"); responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); 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)) => { // Send to frontend - //log::debug!("svg: {svg}"); responses.add(DocumentMessage::RenderScrollbars); - //responses.add(FrontendMessage::UpdateDocumentNodeRender { svg }); let matrix = frame .transform .to_cols_array() @@ -600,30 +587,14 @@ impl NodeGraphExecutor { 1920, 1080, matrix, frame.surface_id.0 ); 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 = "".to_string(); - svg.push_str(&render.svg_defs); - svg.push_str(""); - 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:#?}")); } @@ -643,6 +614,6 @@ impl NodeGraphExecutor { return; } } - warn!("Recieved blob url for invalid segment") + warn!("Received blob url for invalid segment") } } diff --git a/node-graph/README.md b/node-graph/README.md index aa977275..062f6df2 100644 --- a/node-graph/README.md +++ b/node-graph/README.md @@ -2,7 +2,7 @@ ## 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 diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index a4c04c44..bb654477 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -204,6 +204,18 @@ impl<'a, T> AsRef> for EditorApi<'a, T> { } } +// Required for the EndLetNode +impl<'a, IO> From> for Footprint { + fn from(value: EditorApi<'a, IO>) -> Self { + value.render_config.viewport + } +} + +// Required for the EndLetNode +impl<'a, IO> From> for () { + fn from(_value: EditorApi<'a, IO>) -> Self {} +} + #[derive(Debug, Clone, Copy, Default)] pub struct ExtractImageFrame; diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index d04bdc32..86b38199 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -88,6 +88,15 @@ impl SvgRender { self.svg.push(""); } + /// Wraps the SVG with ``, which allows for rotation + pub fn wrap_with_transform(&mut self, transform: DAffine2) { + let defs = &self.svg_defs; + + let svg_header = format!(r#"{defs}"#, format_transform_matrix(transform)); + self.svg.insert(0, svg_header.into()); + self.svg.push(""); + } + pub fn leaf_tag(&mut self, name: impl Into, attributes: impl FnOnce(&mut SvgRenderAttrs)) { self.indent(); self.svg.push("<"); @@ -97,6 +106,11 @@ impl SvgRender { self.svg.push("/>"); } + pub fn leaf_node(&mut self, content: impl Into) { + self.indent(); + self.svg.push(content); + } + pub fn parent_tag(&mut self, name: impl Into, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) { let name = name.into(); self.indent(); @@ -359,6 +373,55 @@ impl GraphicElementRendered for GraphicElementData { } } +/// Used to stop rust complaining about upstream traits adding display implementations to `Option`. 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 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) {} +} + +impl GraphicElementRendered for Option { + 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) {} +} + /// A segment of an svg string to allow for embedding blob urls #[derive(Debug, Clone, PartialEq, Eq)] pub enum SvgSegment { diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 21d26712..94abedc8 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -111,23 +111,24 @@ impl LetNode { /// Caches the output of a given Node and acts as a proxy #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EndLetNode