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:
parent
1093aabc88
commit
b7fe38cf31
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,6 @@
|
|||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::renderer::ClickTarget;
|
||||
use graphene_core::transform::Footprint;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
|
|
@ -9,8 +10,7 @@ use graphene_core::renderer::Quad;
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentMetadata {
|
||||
transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
artboards: HashSet<LayerNodeIdentifier>,
|
||||
folders: HashSet<LayerNodeIdentifier>,
|
||||
|
|
@ -23,7 +23,6 @@ pub struct DocumentMetadata {
|
|||
impl Default for DocumentMetadata {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transforms: HashMap::new(),
|
||||
upstream_transforms: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
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.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));
|
||||
}
|
||||
}
|
||||
|
|
@ -222,38 +220,29 @@ fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a
|
|||
// transforms
|
||||
impl DocumentMetadata {
|
||||
/// 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>) {
|
||||
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;
|
||||
pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
|
||||
self.upstream_transforms = new_upstream_transforms;
|
||||
}
|
||||
|
||||
/// Access the cached transformation to document space from layer space
|
||||
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.transforms.get(&layer).copied().unwrap_or_else(|| {
|
||||
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)
|
||||
self.document_to_viewport.inverse() * self.transform_to_viewport(layer)
|
||||
}
|
||||
|
||||
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 {
|
||||
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,
|
||||
} => {
|
||||
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
|
||||
let parent_transform = document
|
||||
.metadata
|
||||
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
|
||||
let parent_transform = document.metadata.downstream_transform_to_viewport(layer_identifier.to_node());
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
layer,
|
||||
|
|
@ -601,9 +589,7 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
|||
skip_rerender,
|
||||
} => {
|
||||
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
|
||||
let parent_transform = document
|
||||
.metadata
|
||||
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
|
||||
let parent_transform = document.metadata.downstream_transform_to_viewport(layer_identifier.to_node());
|
||||
|
||||
let current_transform = Some(document.metadata.transform_to_viewport(layer_identifier));
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ fn static_nodes() -> Vec<DocumentNodeBlueprint> {
|
|||
DocumentNodeBlueprint {
|
||||
name: "Artboard",
|
||||
category: "General",
|
||||
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _>"),
|
||||
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
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)],
|
||||
properties: node_properties::artboard_properties,
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeBlueprint {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl OriginalTransforms {
|
|||
match self {
|
||||
OriginalTransforms::Layer(layer_map) => {
|
||||
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) => {
|
||||
|
|
@ -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>) {
|
||||
let Some(&original_transform) = original_transform else { return };
|
||||
let parent = layer.parent(&document.metadata);
|
||||
let to = parent.map(|parent| document.metadata.transform_to_viewport(parent)).unwrap_or(document.metadata.document_to_viewport);
|
||||
let to = document.metadata.downstream_transform_to_viewport(layer.to_node());
|
||||
let new = to.inverse() * transformation * to * original_transform;
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer.to_path(),
|
||||
|
|
|
|||
|
|
@ -691,7 +691,6 @@ impl PortfolioMessageHandler {
|
|||
|
||||
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 {
|
||||
warn!("Polling node graph with no document");
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ 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::memo::IORecord;
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList};
|
||||
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::VectorData;
|
||||
|
||||
use graphene_core::{Color, SurfaceFrame, SurfaceId};
|
||||
use graphene_core::{Color, GraphicElementData, SurfaceFrame, SurfaceId};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
|
||||
|
|
@ -56,10 +57,10 @@ pub struct NodeRuntime {
|
|||
imaginate_preferences: ImaginatePreferences,
|
||||
pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
|
||||
pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
pub(crate) transforms: HashMap<NodeId, DAffine2>,
|
||||
pub(crate) upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
pub(crate) upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
graph_hash: Option<u64>,
|
||||
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
|
||||
monitor_nodes: Vec<Vec<NodeId>>,
|
||||
}
|
||||
|
||||
enum NodeRuntimeMessage {
|
||||
|
|
@ -81,8 +82,7 @@ pub(crate) struct GenerationResponse {
|
|||
updates: VecDeque<Message>,
|
||||
new_thumbnails: HashMap<NodeId, SvgSegmentList>,
|
||||
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
new_upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
|
|
@ -109,8 +109,6 @@ thread_local! {
|
|||
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
type MonitorNodes = Vec<Vec<NodeId>>;
|
||||
|
||||
impl NodeRuntime {
|
||||
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
|
||||
let executor = DynamicExecutor::default();
|
||||
|
|
@ -124,9 +122,9 @@ impl NodeRuntime {
|
|||
wasm_io: None,
|
||||
canvas_cache: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
transforms: HashMap::new(),
|
||||
graph_hash: None,
|
||||
upstream_transforms: HashMap::new(),
|
||||
monitor_nodes: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub async fn run(&mut self) {
|
||||
|
|
@ -151,19 +149,18 @@ impl NodeRuntime {
|
|||
..
|
||||
}) => {
|
||||
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();
|
||||
if let Some(ref monitor_nodes) = monitor_nodes {
|
||||
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
||||
self.update_upstream_transforms(monitor_nodes);
|
||||
}
|
||||
|
||||
self.update_thumbnails(&path, &mut responses);
|
||||
self.update_upstream_transforms();
|
||||
|
||||
let response = GenerationResponse {
|
||||
generation_id,
|
||||
result,
|
||||
updates: responses,
|
||||
new_thumbnails: self.thumbnails.clone(),
|
||||
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,
|
||||
};
|
||||
|
|
@ -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() {
|
||||
self.wasm_io = Some(WasmApplicationIo::new().await);
|
||||
}
|
||||
|
|
@ -200,12 +197,10 @@ impl NodeRuntime {
|
|||
self.graph_hash = None;
|
||||
}
|
||||
|
||||
let mut cached_monitor_nodes = None;
|
||||
|
||||
if self.graph_hash.is_none() {
|
||||
let scoped_network = wrap_network_in_scope(graph, font_hash_code);
|
||||
|
||||
let monitor_nodes = scoped_network
|
||||
self.monitor_nodes = scoped_network
|
||||
.recursive_nodes()
|
||||
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
|
||||
.map(|(_, node)| node.path.clone().unwrap_or_default())
|
||||
|
|
@ -216,16 +211,15 @@ impl NodeRuntime {
|
|||
let c = Compiler {};
|
||||
let proto_network = match c.compile_single(scoped_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?");
|
||||
if let Err(e) = self.executor.update(proto_network).await {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +233,7 @@ impl NodeRuntime {
|
|||
};
|
||||
let result = match result {
|
||||
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 {
|
||||
|
|
@ -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.
|
||||
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();
|
||||
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 {
|
||||
warn!("Monitor node has invalid node id");
|
||||
continue;
|
||||
|
|
@ -268,8 +263,7 @@ impl NodeRuntime {
|
|||
continue;
|
||||
};
|
||||
|
||||
let Some(io_data) = value.downcast_ref::<graphene_core::memo::IORecord<Footprint, graphene_core::GraphicElementData>>() else {
|
||||
warn!("Failed to downcast thumbnail to graphic element data");
|
||||
let Some(io_data) = value.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElementData>>() else {
|
||||
continue;
|
||||
};
|
||||
let graphic_element_data = &io_data.output;
|
||||
|
|
@ -283,10 +277,9 @@ impl NodeRuntime {
|
|||
|
||||
let click_targets = self.click_targets.entry(node_id).or_default();
|
||||
click_targets.clear();
|
||||
// Add the graphic element data's click targets to the click targets vector
|
||||
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();
|
||||
if *old_thumbnail != render.svg {
|
||||
responses.add(FrontendMessage::UpdateNodeThumbnail {
|
||||
|
|
@ -305,8 +298,8 @@ impl NodeRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_upstream_transforms(&mut self, monitor_nodes: &[Vec<u64>]) {
|
||||
for node_path in monitor_nodes {
|
||||
pub fn update_upstream_transforms(&mut self) {
|
||||
for node_path in &self.monitor_nodes {
|
||||
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
|
||||
warn!("Monitor node has invalid node id");
|
||||
continue;
|
||||
|
|
@ -315,14 +308,16 @@ impl NodeRuntime {
|
|||
warn!("Failed to introspect monitor node for upstream transforms");
|
||||
continue;
|
||||
};
|
||||
let Some(transform) = value
|
||||
.downcast_ref::<graphene_core::memo::IORecord<Footprint, VectorData>>()
|
||||
.map(|vector_data| vector_data.output.transform())
|
||||
.or_else(|| {
|
||||
value
|
||||
.downcast_ref::<graphene_core::memo::IORecord<Footprint, ImageFrame<Color>>>()
|
||||
.map(|image| image.output.transform())
|
||||
})
|
||||
|
||||
fn try_downcast<T: Transform + 'static>(value: &dyn std::any::Any) -> Option<(Footprint, DAffine2)> {
|
||||
let io_data = value.downcast_ref::<IORecord<Footprint, T>>()?;
|
||||
let transform = io_data.output.transform();
|
||||
Some((io_data.input, 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 {
|
||||
warn!("Failed to downcast transform input");
|
||||
continue;
|
||||
|
|
@ -527,7 +522,6 @@ impl NodeGraphExecutor {
|
|||
updates,
|
||||
new_thumbnails,
|
||||
new_click_targets,
|
||||
new_transforms,
|
||||
new_upstream_transforms,
|
||||
transform,
|
||||
}) => {
|
||||
|
|
@ -566,7 +560,7 @@ impl NodeGraphExecutor {
|
|||
});
|
||||
}
|
||||
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);
|
||||
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())?;
|
||||
|
|
|
|||
|
|
@ -44,16 +44,26 @@ export function createSubscriptionRouter() {
|
|||
// The resulting `message` is an instance of a class that extends `JsMessage`.
|
||||
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.
|
||||
// The frontend should always have a callback for all messages, and so we display an error if one was not found.
|
||||
if (message) {
|
||||
if (callback) callback(message);
|
||||
// eslint-disable-next-line no-console
|
||||
else console.error(`Received a frontend message of type "${messageType}" but no handler was registered for it from the client.`);
|
||||
}
|
||||
// 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.
|
||||
let retries = 0;
|
||||
const callCallback = () => {
|
||||
// It is ok to use constructor.name even with minification since it is used consistently with registerHandler
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::raster::{BlendMode, ImageFrame};
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Node};
|
||||
|
||||
|
|
@ -136,7 +137,8 @@ fn to_graphic_element_data<Data: Into<GraphicElementData>>(graphic_element_data:
|
|||
graphic_element_data.into()
|
||||
}
|
||||
|
||||
pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
|
||||
pub struct ConstructArtboardNode<Contents, Location, Dimensions, Background, Clip> {
|
||||
contents: Contents,
|
||||
location: Location,
|
||||
dimensions: Dimensions,
|
||||
background: Background,
|
||||
|
|
@ -144,7 +146,16 @@ pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
graphic_group,
|
||||
location: location.min(location + dimensions),
|
||||
|
|
|
|||
|
|
@ -284,15 +284,20 @@ impl GraphicElementRendered for Artboard {
|
|||
"g",
|
||||
|attributes| {
|
||||
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 {
|
||||
let id = format!("artboard-{}", generate_uuid());
|
||||
let selector = format!("url(#{id})");
|
||||
use std::fmt::Write;
|
||||
write!(
|
||||
&mut attributes.0.svg_defs,
|
||||
r##"<clipPath id="{id}"><rect x="{}" y="{}" width="{}" height="{}"/></clipPath>"##,
|
||||
self.location.x, self.location.y, self.dimensions.x, self.dimensions.y
|
||||
r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}" transform="{}"/></clipPath>"##,
|
||||
self.dimensions.x,
|
||||
self.dimensions.y,
|
||||
format_transform_matrix(self.graphic_group.transform.inverse())
|
||||
)
|
||||
.unwrap();
|
||||
attributes.push("clip-path", selector);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ pub trait Node<'i, Input: 'i>: 'i {
|
|||
fn eval(&'i self, input: Input) -> Self::Output;
|
||||
/// Resets the node, e.g. the LetNode's cache is set to None.
|
||||
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.
|
||||
#[cfg(feature = "std")]
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
|
||||
|
|
@ -98,10 +102,6 @@ where
|
|||
Self::Output: 'i + StaticTypeSized,
|
||||
Input: 'i + StaticTypeSized,
|
||||
{
|
||||
fn node_name(&self) -> &'static str {
|
||||
core::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn input_type(&self) -> TypeId {
|
||||
TypeId::of::<Input::Static>()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::raster::bbox::AxisAlignedBbox;
|
|||
use crate::raster::ImageFrame;
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::VectorData;
|
||||
use crate::Artboard;
|
||||
use crate::GraphicElementData;
|
||||
use crate::GraphicGroup;
|
||||
use crate::Node;
|
||||
|
|
@ -76,7 +77,7 @@ impl Transform for GraphicElementData {
|
|||
GraphicElementData::ImageFrame(image_frame) => image_frame.transform(),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
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 {
|
||||
|
|
@ -85,7 +86,7 @@ impl Transform for GraphicElementData {
|
|||
GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
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 {
|
||||
|
|
@ -94,7 +95,7 @@ impl Transform for GraphicElementData {
|
|||
GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
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::Text(_) => todo!("Transform of text"),
|
||||
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 {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*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: GraphicGroup, 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();
|
||||
for (id, c, types) in node_types.into_iter().flatten() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue