mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-07 07:38:11 +08:00
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:
co-authored by
0hypercube
Keavon Chambers
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-16
@@ -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);
|
||||
|
||||
+2
-1
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user