Fix how transforms work with footprints and remove a redundant transforms field (#1484)

* Prune unused thumbnails in node graph executor

* Fix transform downcasting failure for GraphicElementData

* Remove more warnings

* Revert upstream transform calculation change

* Use footprint to calculate layer transforms

* Fix artboards

* Move artwork with artboard

* Remove Keavon's warnings

* Prevent misordered FrontendMessages failing to reach JS handlers

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2023-12-03 14:17:28 -08:00
committed by GitHub
co-authored by 0hypercube Keavon Chambers
parent 1093aabc88
commit b7fe38cf31
14 changed files with 116 additions and 112 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -25
View File
@@ -1,5 +1,6 @@
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_core::renderer::ClickTarget; use graphene_core::renderer::ClickTarget;
use graphene_core::transform::Footprint;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::num::NonZeroU64; use std::num::NonZeroU64;
@@ -9,8 +10,7 @@ use graphene_core::renderer::Quad;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DocumentMetadata { pub struct DocumentMetadata {
transforms: HashMap<LayerNodeIdentifier, DAffine2>, upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
upstream_transforms: HashMap<NodeId, DAffine2>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>, structure: HashMap<LayerNodeIdentifier, NodeRelations>,
artboards: HashSet<LayerNodeIdentifier>, artboards: HashSet<LayerNodeIdentifier>,
folders: HashSet<LayerNodeIdentifier>, folders: HashSet<LayerNodeIdentifier>,
@@ -23,7 +23,6 @@ pub struct DocumentMetadata {
impl Default for DocumentMetadata { impl Default for DocumentMetadata {
fn default() -> Self { fn default() -> Self {
Self { Self {
transforms: HashMap::new(),
upstream_transforms: HashMap::new(), upstream_transforms: HashMap::new(),
click_targets: HashMap::new(), click_targets: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]), structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
@@ -207,7 +206,6 @@ impl DocumentMetadata {
self.selected_nodes.retain(|node| graph.nodes.contains_key(node)); self.selected_nodes.retain(|node| graph.nodes.contains_key(node));
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node)); self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
self.transforms.retain(|layer, _| self.structure.contains_key(layer));
self.click_targets.retain(|layer, _| self.structure.contains_key(layer)); self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
} }
} }
@@ -222,38 +220,29 @@ fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a
// transforms // transforms
impl DocumentMetadata { impl DocumentMetadata {
/// Update the cached transforms of the layers /// Update the cached transforms of the layers
pub fn update_transforms(&mut self, mut new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>) { pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
let mut stack = vec![(LayerNodeIdentifier::ROOT, DAffine2::IDENTITY)];
while let Some((layer, transform)) = stack.pop() {
for child in layer.children(self) {
let Some(new_transform) = new_transforms.get_mut(&child) else { continue };
*new_transform = transform * *new_transform;
stack.push((child, *new_transform));
}
}
self.transforms = new_transforms;
self.upstream_transforms = new_upstream_transforms; self.upstream_transforms = new_upstream_transforms;
} }
/// Access the cached transformation to document space from layer space /// Access the cached transformation to document space from layer space
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.transforms.get(&layer).copied().unwrap_or_else(|| { self.document_to_viewport.inverse() * self.transform_to_viewport(layer)
warn!("Tried to access transform of bad layer {layer:?}");
DAffine2::IDENTITY
})
}
pub fn local_transform(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.transform_to_document(layer.parent(self).unwrap_or_default()).inverse() * self.transform_to_document(layer)
} }
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.document_to_viewport * self.transform_to_document(layer) self.upstream_transforms
.get(&layer.to_node())
.copied()
.map(|(footprint, transform)| footprint.transform * transform)
.unwrap_or(DAffine2::IDENTITY)
} }
pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 { pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 {
self.upstream_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY) self.upstream_transforms.get(&node_id).copied().map(|(_, transform)| transform).unwrap_or(DAffine2::IDENTITY)
}
pub fn downstream_transform_to_viewport(&self, node_id: NodeId) -> DAffine2 {
self.upstream_transforms.get(&node_id).copied().map(|(footprint, _)| footprint.transform).unwrap_or(DAffine2::IDENTITY)
} }
} }
@@ -576,23 +576,11 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
skip_rerender, skip_rerender,
} => { } => {
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network); let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
let parent_transform = document let parent_transform = document.metadata.downstream_transform_to_viewport(layer_identifier.to_node());
.metadata
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender); modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
} }
let transform = transform.to_cols_array();
responses.add(match transform_in {
TransformIn::Local => Operation::TransformLayer { path: layer, transform },
TransformIn::Scope { scope } => {
let scope = scope.to_cols_array();
Operation::TransformLayerInScope { path: layer, transform, scope }
}
TransformIn::Viewport => Operation::TransformLayerInViewport { path: layer, transform },
});
} }
GraphOperationMessage::TransformSet { GraphOperationMessage::TransformSet {
layer, layer,
@@ -601,9 +589,7 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
skip_rerender, skip_rerender,
} => { } => {
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network); let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
let parent_transform = document let parent_transform = document.metadata.downstream_transform_to_viewport(layer_identifier.to_node());
.metadata
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
let current_transform = Some(document.metadata.transform_to_viewport(layer_identifier)); let current_transform = Some(document.metadata.transform_to_viewport(layer_identifier));
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
@@ -258,7 +258,7 @@ fn static_nodes() -> Vec<DocumentNodeBlueprint> {
DocumentNodeBlueprint { DocumentNodeBlueprint {
name: "Artboard", name: "Artboard",
category: "General", category: "General",
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _>"), identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _>"),
inputs: vec![ inputs: vec![
DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
DocumentInputType::value("Location", TaggedValue::IVec2(glam::IVec2::ZERO), false), DocumentInputType::value("Location", TaggedValue::IVec2(glam::IVec2::ZERO), false),
@@ -268,6 +268,7 @@ fn static_nodes() -> Vec<DocumentNodeBlueprint> {
], ],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)], outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
properties: node_properties::artboard_properties, properties: node_properties::artboard_properties,
manual_composition: Some(concrete!(Footprint)),
..Default::default() ..Default::default()
}, },
DocumentNodeBlueprint { DocumentNodeBlueprint {
@@ -34,7 +34,7 @@ impl OriginalTransforms {
match self { match self {
OriginalTransforms::Layer(layer_map) => { OriginalTransforms::Layer(layer_map) => {
for &layer in selected { for &layer in selected {
layer_map.entry(layer).or_insert_with(|| document.metadata.local_transform(layer)); layer_map.entry(layer).or_insert_with(|| document.metadata.upstream_transform(layer.to_node()));
} }
} }
OriginalTransforms::Path(path_map) => { OriginalTransforms::Path(path_map) => {
@@ -368,8 +368,7 @@ impl<'a> Selected<'a> {
fn transform_layer(document: &Document, layer: LayerNodeIdentifier, original_transform: Option<&DAffine2>, transformation: DAffine2, responses: &mut VecDeque<Message>) { fn transform_layer(document: &Document, layer: LayerNodeIdentifier, original_transform: Option<&DAffine2>, transformation: DAffine2, responses: &mut VecDeque<Message>) {
let Some(&original_transform) = original_transform else { return }; let Some(&original_transform) = original_transform else { return };
let parent = layer.parent(&document.metadata); let to = document.metadata.downstream_transform_to_viewport(layer.to_node());
let to = parent.map(|parent| document.metadata.transform_to_viewport(parent)).unwrap_or(document.metadata.document_to_viewport);
let new = to.inverse() * transformation * to * original_transform; let new = to.inverse() * transformation * to * original_transform;
responses.add(GraphOperationMessage::TransformSet { responses.add(GraphOperationMessage::TransformSet {
layer: layer.to_path(), layer: layer.to_path(),
@@ -691,7 +691,6 @@ impl PortfolioMessageHandler {
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) { pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else { let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
warn!("Polling node graph with no document");
return; return;
}; };
+35 -41
View File
@@ -14,6 +14,7 @@ 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::memo::IORecord;
use graphene_core::raster::{Image, ImageFrame}; use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList}; use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache; use graphene_core::text::FontCache;
@@ -21,7 +22,7 @@ use graphene_core::transform::{Footprint, Transform};
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::vector::VectorData; use graphene_core::vector::VectorData;
use graphene_core::{Color, SurfaceFrame, SurfaceId}; use graphene_core::{Color, GraphicElementData, SurfaceFrame, SurfaceId};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::dynamic_executor::DynamicExecutor;
@@ -56,10 +57,10 @@ pub struct NodeRuntime {
imaginate_preferences: ImaginatePreferences, imaginate_preferences: ImaginatePreferences,
pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>, pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>, pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub(crate) transforms: HashMap<NodeId, DAffine2>, pub(crate) upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
pub(crate) upstream_transforms: HashMap<NodeId, DAffine2>,
graph_hash: Option<u64>, graph_hash: Option<u64>,
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>, canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
monitor_nodes: Vec<Vec<NodeId>>,
} }
enum NodeRuntimeMessage { enum NodeRuntimeMessage {
@@ -81,8 +82,7 @@ pub(crate) struct GenerationResponse {
updates: VecDeque<Message>, updates: VecDeque<Message>,
new_thumbnails: HashMap<NodeId, SvgSegmentList>, new_thumbnails: HashMap<NodeId, SvgSegmentList>,
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
new_upstream_transforms: HashMap<NodeId, DAffine2>,
transform: DAffine2, transform: DAffine2,
} }
@@ -109,8 +109,6 @@ thread_local! {
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None)); pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
} }
type MonitorNodes = Vec<Vec<NodeId>>;
impl NodeRuntime { impl NodeRuntime {
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self { fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
let executor = DynamicExecutor::default(); let executor = DynamicExecutor::default();
@@ -124,9 +122,9 @@ impl NodeRuntime {
wasm_io: None, wasm_io: None,
canvas_cache: HashMap::new(), canvas_cache: HashMap::new(),
click_targets: HashMap::new(), click_targets: HashMap::new(),
transforms: HashMap::new(),
graph_hash: None, graph_hash: None,
upstream_transforms: HashMap::new(), upstream_transforms: HashMap::new(),
monitor_nodes: Vec::new(),
} }
} }
pub async fn run(&mut self) { pub async fn run(&mut self) {
@@ -151,19 +149,18 @@ impl NodeRuntime {
.. ..
}) => { }) => {
let transform = render_config.viewport.transform; let transform = render_config.viewport.transform;
let (result, monitor_nodes) = self.execute_network(&path, graph, render_config).await; let result = self.execute_network(&path, graph, render_config).await;
let mut responses = VecDeque::new(); let mut responses = VecDeque::new();
if let Some(ref monitor_nodes) = monitor_nodes {
self.update_thumbnails(&path, monitor_nodes, &mut responses); self.update_thumbnails(&path, &mut responses);
self.update_upstream_transforms(monitor_nodes); self.update_upstream_transforms();
}
let response = GenerationResponse { let response = GenerationResponse {
generation_id, generation_id,
result, result,
updates: responses, updates: responses,
new_thumbnails: self.thumbnails.clone(), new_thumbnails: self.thumbnails.clone(),
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_upstream_transforms: self.upstream_transforms.clone(), new_upstream_transforms: self.upstream_transforms.clone(),
transform, transform,
}; };
@@ -173,7 +170,7 @@ impl NodeRuntime {
} }
} }
async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, render_config: RenderConfig) -> (Result<TaggedValue, String>, Option<MonitorNodes>) { async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, render_config: RenderConfig) -> Result<TaggedValue, String> {
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);
} }
@@ -200,12 +197,10 @@ impl NodeRuntime {
self.graph_hash = None; self.graph_hash = None;
} }
let mut cached_monitor_nodes = None;
if self.graph_hash.is_none() { if self.graph_hash.is_none() {
let scoped_network = wrap_network_in_scope(graph, font_hash_code); let scoped_network = wrap_network_in_scope(graph, font_hash_code);
let monitor_nodes = scoped_network self.monitor_nodes = scoped_network
.recursive_nodes() .recursive_nodes()
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>")) .filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
.map(|(_, node)| node.path.clone().unwrap_or_default()) .map(|(_, node)| node.path.clone().unwrap_or_default())
@@ -216,16 +211,15 @@ impl NodeRuntime {
let c = Compiler {}; let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) { let proto_network = match c.compile_single(scoped_network) {
Ok(network) => network, Ok(network) => network,
Err(e) => return (Err(e), Some(monitor_nodes)), Err(e) => return Err(e),
}; };
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
if let Err(e) = self.executor.update(proto_network).await { if let Err(e) = self.executor.update(proto_network).await {
error!("Failed to update executor:\n{e}"); error!("Failed to update executor:\n{e}");
return (Err(e), Some(monitor_nodes)); return Err(e);
} }
cached_monitor_nodes = Some(monitor_nodes);
self.graph_hash = Some(hash_code); self.graph_hash = Some(hash_code);
} }
@@ -239,7 +233,7 @@ impl NodeRuntime {
}; };
let result = match result { let result = match result {
Ok(value) => value, Ok(value) => value,
Err(e) => return (Err(e), cached_monitor_nodes), Err(e) => return Err(e),
}; };
if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result { if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
@@ -252,13 +246,14 @@ impl NodeRuntime {
} }
} }
} }
(Ok(result), cached_monitor_nodes) Ok(result)
} }
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI. /// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: &[Vec<u64>], responses: &mut VecDeque<Message>) { pub fn update_thumbnails(&mut self, layer_path: &[LayerId], responses: &mut VecDeque<Message>) {
let mut image_data: Vec<_> = Vec::new(); let mut image_data: Vec<_> = Vec::new();
for node_path in monitor_nodes { self.thumbnails.retain(|id, _| self.monitor_nodes.iter().any(|node_path| node_path.contains(id)));
for node_path in &self.monitor_nodes {
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else { let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
warn!("Monitor node has invalid node id"); warn!("Monitor node has invalid node id");
continue; continue;
@@ -268,8 +263,7 @@ impl NodeRuntime {
continue; continue;
}; };
let Some(io_data) = value.downcast_ref::<graphene_core::memo::IORecord<Footprint, graphene_core::GraphicElementData>>() else { let Some(io_data) = value.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElementData>>() else {
warn!("Failed to downcast thumbnail to graphic element data");
continue; continue;
}; };
let graphic_element_data = &io_data.output; let graphic_element_data = &io_data.output;
@@ -283,10 +277,9 @@ impl NodeRuntime {
let click_targets = self.click_targets.entry(node_id).or_default(); let click_targets = self.click_targets.entry(node_id).or_default();
click_targets.clear(); click_targets.clear();
// Add the graphic element data's click targets to the click targets vector
graphic_element_data.add_click_targets(click_targets); graphic_element_data.add_click_targets(click_targets);
self.transforms.insert(node_id, graphic_element_data.transform());
let old_thumbnail = self.thumbnails.entry(node_id).or_default(); let old_thumbnail = self.thumbnails.entry(node_id).or_default();
if *old_thumbnail != render.svg { if *old_thumbnail != render.svg {
responses.add(FrontendMessage::UpdateNodeThumbnail { responses.add(FrontendMessage::UpdateNodeThumbnail {
@@ -305,8 +298,8 @@ impl NodeRuntime {
} }
} }
pub fn update_upstream_transforms(&mut self, monitor_nodes: &[Vec<u64>]) { pub fn update_upstream_transforms(&mut self) {
for node_path in monitor_nodes { for node_path in &self.monitor_nodes {
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else { let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
warn!("Monitor node has invalid node id"); warn!("Monitor node has invalid node id");
continue; continue;
@@ -315,14 +308,16 @@ impl NodeRuntime {
warn!("Failed to introspect monitor node for upstream transforms"); warn!("Failed to introspect monitor node for upstream transforms");
continue; continue;
}; };
let Some(transform) = value
.downcast_ref::<graphene_core::memo::IORecord<Footprint, VectorData>>() fn try_downcast<T: Transform + 'static>(value: &dyn std::any::Any) -> Option<(Footprint, DAffine2)> {
.map(|vector_data| vector_data.output.transform()) let io_data = value.downcast_ref::<IORecord<Footprint, T>>()?;
.or_else(|| { let transform = io_data.output.transform();
value Some((io_data.input, transform))
.downcast_ref::<graphene_core::memo::IORecord<Footprint, ImageFrame<Color>>>() }
.map(|image| image.output.transform())
}) let Some(transform) = try_downcast::<VectorData>(value.as_ref())
.or_else(|| try_downcast::<ImageFrame<Color>>(value.as_ref()))
.or_else(|| try_downcast::<GraphicElementData>(value.as_ref()))
else { else {
warn!("Failed to downcast transform input"); warn!("Failed to downcast transform input");
continue; continue;
@@ -527,7 +522,6 @@ impl NodeGraphExecutor {
updates, updates,
new_thumbnails, new_thumbnails,
new_click_targets, new_click_targets,
new_transforms,
new_upstream_transforms, new_upstream_transforms,
transform, transform,
}) => { }) => {
@@ -566,7 +560,7 @@ impl NodeGraphExecutor {
}); });
} }
self.thumbnails = new_thumbnails; self.thumbnails = new_thumbnails;
document.metadata.update_transforms(new_transforms, new_upstream_transforms); document.metadata.update_transforms(new_upstream_transforms);
document.metadata.update_click_targets(new_click_targets); document.metadata.update_click_targets(new_click_targets);
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())?;
@@ -44,16 +44,26 @@ export function createSubscriptionRouter() {
// The resulting `message` is an instance of a class that extends `JsMessage`. // The resulting `message` is an instance of a class that extends `JsMessage`.
const message = messageIsClass ? plainToInstance(messageMaker, unwrappedMessageData) : messageMaker(unwrappedMessageData, wasm, instance); const message = messageIsClass ? plainToInstance(messageMaker, unwrappedMessageData) : messageMaker(unwrappedMessageData, wasm, instance);
// It is ok to use constructor.name even with minification since it is used consistently with registerHandler
const callback = subscriptions[message.constructor.name];
// If we have constructed a valid message, then we try and execute the callback that the frontend has associated with this message. // If we have constructed a valid message, then we try and execute the callback that the frontend has associated with this message.
// The frontend should always have a callback for all messages, and so we display an error if one was not found. // The frontend should always have a callback for all messages, but due to message ordering, we might have to delay a few stack frames until we do.
if (message) { let retries = 0;
if (callback) callback(message); const callCallback = () => {
// eslint-disable-next-line no-console // It is ok to use constructor.name even with minification since it is used consistently with registerHandler
else console.error(`Received a frontend message of type "${messageType}" but no handler was registered for it from the client.`); const callback = subscriptions[message.constructor.name];
}
// Attempt to call the callback, but try again several times on the next stack frame if it is not yet registered due to message ordering.
if (callback) {
callback(message);
} else if (retries <= 3) {
retries += 1;
setTimeout(callCallback, 0);
} else {
// eslint-disable-next-line no-console
console.error(`Received a frontend message of type "${messageType}" but no handler was registered for it from the client.`);
}
};
callCallback();
}; };
return { return {
+13 -2
View File
@@ -1,4 +1,5 @@
use crate::raster::{BlendMode, ImageFrame}; use crate::raster::{BlendMode, ImageFrame};
use crate::transform::Footprint;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::{Color, Node}; use crate::{Color, Node};
@@ -136,7 +137,8 @@ fn to_graphic_element_data<Data: Into<GraphicElementData>>(graphic_element_data:
graphic_element_data.into() graphic_element_data.into()
} }
pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> { pub struct ConstructArtboardNode<Contents, Location, Dimensions, Background, Clip> {
contents: Contents,
location: Location, location: Location,
dimensions: Dimensions, dimensions: Dimensions,
background: Background, background: Background,
@@ -144,7 +146,16 @@ pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
} }
#[node_fn(ConstructArtboardNode)] #[node_fn(ConstructArtboardNode)]
fn construct_artboard(graphic_group: GraphicGroup, location: IVec2, dimensions: IVec2, background: Color, clip: bool) -> Artboard { async fn construct_artboard<Fut: Future<Output = GraphicGroup>>(
mut footprint: Footprint,
contents: impl Node<Footprint, Output = Fut>,
location: IVec2,
dimensions: IVec2,
background: Color,
clip: bool,
) -> Artboard {
footprint.transform = footprint.transform * DAffine2::from_translation(location.as_dvec2());
let graphic_group = self.contents.eval(footprint).await;
Artboard { Artboard {
graphic_group, graphic_group,
location: location.min(location + dimensions), location: location.min(location + dimensions),
@@ -284,15 +284,20 @@ impl GraphicElementRendered for Artboard {
"g", "g",
|attributes| { |attributes| {
attributes.push("class", "artboard"); attributes.push("class", "artboard");
attributes.push("transform", format_transform_matrix(self.graphic_group.transform)); attributes.push(
"transform",
format_transform_matrix(DAffine2::from_translation(self.location.as_dvec2()) * self.graphic_group.transform),
);
if self.clip { if self.clip {
let id = format!("artboard-{}", generate_uuid()); let id = format!("artboard-{}", generate_uuid());
let selector = format!("url(#{id})"); let selector = format!("url(#{id})");
use std::fmt::Write; use std::fmt::Write;
write!( write!(
&mut attributes.0.svg_defs, &mut attributes.0.svg_defs,
r##"<clipPath id="{id}"><rect x="{}" y="{}" width="{}" height="{}"/></clipPath>"##, r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}" transform="{}"/></clipPath>"##,
self.location.x, self.location.y, self.dimensions.x, self.dimensions.y self.dimensions.x,
self.dimensions.y,
format_transform_matrix(self.graphic_group.transform.inverse())
) )
.unwrap(); .unwrap();
attributes.push("clip-path", selector); attributes.push("clip-path", selector);
+4 -4
View File
@@ -54,6 +54,10 @@ pub trait Node<'i, Input: 'i>: 'i {
fn eval(&'i self, input: Input) -> Self::Output; fn eval(&'i self, input: Input) -> Self::Output;
/// Resets the node, e.g. the LetNode's cache is set to None. /// Resets the node, e.g. the LetNode's cache is set to None.
fn reset(&self) {} fn reset(&self) {}
/// Returns the name of the node for diagnostic purposes.
fn node_name(&self) -> &'static str {
core::any::type_name::<Self>()
}
/// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes. /// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes.
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> { fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
@@ -98,10 +102,6 @@ where
Self::Output: 'i + StaticTypeSized, Self::Output: 'i + StaticTypeSized,
Input: 'i + StaticTypeSized, Input: 'i + StaticTypeSized,
{ {
fn node_name(&self) -> &'static str {
core::any::type_name::<Self>()
}
fn input_type(&self) -> TypeId { fn input_type(&self) -> TypeId {
TypeId::of::<Input::Static>() TypeId::of::<Input::Static>()
} }
+14 -4
View File
@@ -9,6 +9,7 @@ use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::ImageFrame; use crate::raster::ImageFrame;
use crate::raster::Pixel; use crate::raster::Pixel;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::Artboard;
use crate::GraphicElementData; use crate::GraphicElementData;
use crate::GraphicGroup; use crate::GraphicGroup;
use crate::Node; use crate::Node;
@@ -76,7 +77,7 @@ impl Transform for GraphicElementData {
GraphicElementData::ImageFrame(image_frame) => image_frame.transform(), GraphicElementData::ImageFrame(image_frame) => image_frame.transform(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform(), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElementData::Artboard(artboard) => artboard.graphic_group.transform(), GraphicElementData::Artboard(artboard) => artboard.transform(),
} }
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 { fn local_pivot(&self, pivot: DVec2) -> DVec2 {
@@ -85,7 +86,7 @@ impl Transform for GraphicElementData {
GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot), GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElementData::Artboard(artboard) => artboard.graphic_group.local_pivot(pivot), GraphicElementData::Artboard(artboard) => artboard.local_pivot(pivot),
} }
} }
fn decompose_scale(&self) -> DVec2 { fn decompose_scale(&self) -> DVec2 {
@@ -94,7 +95,7 @@ impl Transform for GraphicElementData {
GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(), GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.decompose_scale(), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.decompose_scale(),
GraphicElementData::Artboard(artboard) => artboard.graphic_group.decompose_scale(), GraphicElementData::Artboard(artboard) => artboard.decompose_scale(),
} }
} }
} }
@@ -105,7 +106,7 @@ impl TransformMut for GraphicElementData {
GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(), GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform_mut(), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElementData::Artboard(artboard) => artboard.graphic_group.transform_mut(), GraphicElementData::Artboard(_) => todo!("Transform of artboard"),
} }
} }
} }
@@ -124,6 +125,15 @@ impl TransformMut for VectorData {
} }
} }
impl Transform for Artboard {
fn transform(&self) -> DAffine2 {
DAffine2::IDENTITY
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot
}
}
impl Transform for DAffine2 { impl Transform for DAffine2 {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
*self *self
@@ -844,7 +844,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []), register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementData, input: GraphicGroup, params: []), register_node!(graphene_core::ToGraphicElementData, input: GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicElementData, input: Artboard, params: []), register_node!(graphene_core::ToGraphicElementData, input: Artboard, params: []),
register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]), async_node!(graphene_core::ConstructArtboardNode<_, _, _, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => GraphicGroup, () => 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() {