mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 16:08:12 +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 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-16
@@ -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);
|
||||||
|
|||||||
+2
-1
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user