Skip to content

Commit a696aae

Browse files
Keavonadamgerhant0HyperCube
authored
Instance tables refactor part 5: unwrap GraphicGroup as multi-row Instance<GraphicElement> tables and move up transforms (#2363)
* Just group * Partly working but without transforms * Remove Transform/TransformMut from GraphicElement and GraphicGroupTable * Fix layers and flattening * Fix transform group handling on the remaining nodes * Change collect metadata * Add transform on vector data. TODO: Remove duplicate transform * Small code tidying-up * Add concatenate node? * Remove ignore_modifications which is always false * Improve transforms * Mostly fix the nested transform cage angle (except leaf layers and skew) * WIP attempt to integrate skew * Fix nesting bounding box * Avoid setting the transform * Fix stroke transforms * Renderer cleanup * Fix tests for repeated elements not given unique point IDs * Suppress cargo-deny warning * Fix upgrade code for graphic group data * Work around rendering issue in Isometric Fountain --------- Co-authored-by: Adam <[email protected]> Co-authored-by: hypercube <[email protected]>
1 parent d2fc919 commit a696aae

28 files changed

+858
-932
lines changed

demo-artwork/isometric-fountain.graphite

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deny.toml

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ ignore = [
4747
"RUSTSEC-2024-0388", # Unmaintained but still fully functional crate `derivative`
4848
"RUSTSEC-2025-0007", # Unmaintained but still fully functional crate `ring`
4949
"RUSTSEC-2024-0436", # Unmaintained but still fully functional crate `paste`
50+
"RUSTSEC-2025-0014", # Unmaintained but still fully functional crate `humantime`
5051
]
5152
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
5253
# lower than the range specified will be ignored. Note that ignored advisories

editor/src/dispatcher.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,18 @@ impl Dispatcher {
141141
};
142142

143143
let graphene_std::renderer::RenderMetadata {
144-
footprints,
144+
upstream_footprints: footprints,
145+
local_transforms,
145146
click_targets,
146147
clip_targets,
147148
} = render_metadata;
148149

149150
// Run these update state messages immediately
150151
let messages = [
151-
DocumentMessage::UpdateUpstreamTransforms { upstream_transforms: footprints },
152+
DocumentMessage::UpdateUpstreamTransforms {
153+
upstream_footprints: footprints,
154+
local_transforms,
155+
},
152156
DocumentMessage::UpdateClickTargets { click_targets },
153157
DocumentMessage::UpdateClipTargets { clip_targets },
154158
];

editor/src/messages/portfolio/document/document_message.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ pub enum DocumentMessage {
179179
ToggleOverlaysVisibility,
180180
ToggleSnapping,
181181
UpdateUpstreamTransforms {
182-
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
182+
upstream_footprints: HashMap<NodeId, Footprint>,
183+
local_transforms: HashMap<NodeId, DAffine2>,
183184
},
184185
UpdateClickTargets {
185186
click_targets: HashMap<NodeId, Vec<ClickTarget>>,

editor/src/messages/portfolio/document/document_message_handler.rs

+43-40
Original file line numberDiff line numberDiff line change
@@ -1233,8 +1233,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
12331233
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
12341234
responses.add(PortfolioMessage::UpdateDocumentWidgets);
12351235
}
1236-
DocumentMessage::UpdateUpstreamTransforms { upstream_transforms } => {
1237-
self.network_interface.update_transforms(upstream_transforms);
1236+
DocumentMessage::UpdateUpstreamTransforms {
1237+
upstream_footprints,
1238+
local_transforms,
1239+
} => {
1240+
self.network_interface.update_transforms(upstream_footprints, local_transforms);
12381241
}
12391242
DocumentMessage::UpdateClickTargets { click_targets } => {
12401243
// TODO: Allow non layer nodes to have click targets
@@ -1634,6 +1637,44 @@ impl DocumentMessageHandler {
16341637
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
16351638
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
16361639
.or_else(|_| {
1640+
// TODO: Eventually remove this document upgrade code
1641+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1642+
pub struct OldDocumentMessageHandler {
1643+
// ============================================
1644+
// Fields that are saved in the document format
1645+
// ============================================
1646+
//
1647+
/// The node graph that generates this document's artwork.
1648+
/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
1649+
pub network: OldNodeNetwork,
1650+
/// List of the [`NodeId`]s that are currently selected by the user.
1651+
pub selected_nodes: SelectedNodes,
1652+
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
1653+
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
1654+
pub collapsed: CollapsedLayers,
1655+
/// The name of the document, which is displayed in the tab and title bar of the editor.
1656+
pub name: String,
1657+
/// The full Git commit hash of the Graphite repository that was used to build the editor.
1658+
/// We save this to provide a hint about which version of the editor was used to create the document.
1659+
pub commit_hash: String,
1660+
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
1661+
pub document_ptz: PTZ,
1662+
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
1663+
pub document_mode: DocumentMode,
1664+
/// The current view mode that the user has set for rendering the document within the viewport.
1665+
/// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently.
1666+
pub view_mode: ViewMode,
1667+
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
1668+
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
1669+
pub overlays_visible: bool,
1670+
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
1671+
pub rulers_visible: bool,
1672+
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
1673+
pub graph_view_overlay_open: bool,
1674+
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
1675+
pub snapping_state: SnappingState,
1676+
}
1677+
16371678
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
16381679
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
16391680
collapsed: old_message_handler.collapsed,
@@ -2639,41 +2680,3 @@ impl Iterator for ClickXRayIter<'_> {
26392680
None
26402681
}
26412682
}
2642-
2643-
// TODO: Eventually remove this document upgrade code
2644-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
2645-
pub struct OldDocumentMessageHandler {
2646-
// ============================================
2647-
// Fields that are saved in the document format
2648-
// ============================================
2649-
//
2650-
/// The node graph that generates this document's artwork.
2651-
/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
2652-
pub network: OldNodeNetwork,
2653-
/// List of the [`NodeId`]s that are currently selected by the user.
2654-
pub selected_nodes: SelectedNodes,
2655-
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
2656-
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
2657-
pub collapsed: CollapsedLayers,
2658-
/// The name of the document, which is displayed in the tab and title bar of the editor.
2659-
pub name: String,
2660-
/// The full Git commit hash of the Graphite repository that was used to build the editor.
2661-
/// We save this to provide a hint about which version of the editor was used to create the document.
2662-
pub commit_hash: String,
2663-
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
2664-
pub document_ptz: PTZ,
2665-
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
2666-
pub document_mode: DocumentMode,
2667-
/// The current view mode that the user has set for rendering the document within the viewport.
2668-
/// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently.
2669-
pub view_mode: ViewMode,
2670-
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
2671-
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
2672-
pub overlays_visible: bool,
2673-
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
2674-
pub rulers_visible: bool,
2675-
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
2676-
pub graph_view_overlay_open: bool,
2677-
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
2678-
pub snapping_state: SnappingState,
2679-
}

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

+31-24
Original file line numberDiff line numberDiff line change
@@ -237,45 +237,52 @@ impl<'a> ModifyInputsContext<'a> {
237237
}
238238
})
239239
}
240+
240241
/// Gets the node id of a node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
241242
/// The returned node is based on the selection dots in the layer. The right most dot will always insert/access the path that flows directly into the layer.
242243
/// Each dot after that represents an existing path node. If there is an existing upstream node, then it will always be returned first.
243-
pub fn existing_node_id(&mut self, reference: &'static str, create_if_nonexistent: bool) -> Option<NodeId> {
244+
pub fn existing_node_id(&mut self, reference_name: &'static str, create_if_nonexistent: bool) -> Option<NodeId> {
244245
// Start from the layer node or export
245246
let output_layer = self.get_output_layer()?;
246247

247-
let upstream = self
248-
.network_interface
249-
.upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
248+
let existing_node_id = Self::locate_node_in_layer_chain(reference_name, output_layer, self.network_interface);
249+
250+
// Create a new node if the node does not exist and update its inputs
251+
if create_if_nonexistent {
252+
return existing_node_id.or_else(|| self.create_node(reference_name));
253+
}
250254

251-
// Take until another layer node is found (but not the first layer node)
252-
let mut existing_node_id = None;
253-
for upstream_node in upstream.collect::<Vec<_>>() {
255+
existing_node_id
256+
}
257+
258+
/// Gets the node id of a node with a specific reference (name) that is upstream (leftward) from the layer node, but before reaching another upstream layer stack.
259+
/// For example, if given a group layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the group layer and its layer stack child contents.
260+
/// It would also travel up an entire layer that's not fed by a stack until reaching the generator node, such as a "Rectangle" or "Path" layer.
261+
pub fn locate_node_in_layer_chain(reference_name: &str, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
262+
let upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
263+
264+
// Look at all of the upstream nodes
265+
for upstream_node in upstream {
254266
// Check if this is the node we have been searching for.
255-
if self
256-
.network_interface
267+
if network_interface
257268
.reference(&upstream_node, &[])
258-
.is_some_and(|node_reference| *node_reference == Some(reference.to_string()))
269+
.is_some_and(|node_reference| *node_reference == Some(reference_name.to_string()))
259270
{
260-
existing_node_id = Some(upstream_node);
261-
break;
262-
}
263-
264-
let is_traversal_start = |node_id: NodeId| {
265-
self.layer_node.map(|layer| layer.to_node()) == Some(node_id) || self.network_interface.document_network().exports.iter().any(|export| export.as_node() == Some(node_id))
266-
};
271+
if !network_interface.is_visible(&upstream_node, &[]) {
272+
continue;
273+
}
267274

268-
if !is_traversal_start(upstream_node) && (self.network_interface.is_layer(&upstream_node, &[])) {
269-
break;
275+
return Some(upstream_node);
270276
}
271-
}
272277

273-
// Create a new node if the node does not exist and update its inputs
274-
if create_if_nonexistent {
275-
return existing_node_id.or_else(|| self.create_node(reference));
278+
// Take until another layer node is found (but not the first layer node)
279+
let is_traversal_start = |node_id: NodeId| left_of_layer.to_node() == node_id || network_interface.document_network().exports.iter().any(|export| export.as_node() == Some(node_id));
280+
if !is_traversal_start(upstream_node) && (network_interface.is_layer(&upstream_node, &[])) {
281+
return None;
282+
}
276283
}
277284

278-
existing_node_id
285+
None
279286
}
280287

281288
/// Create a new node inside the layer

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -2255,7 +2255,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
22552255
..Default::default()
22562256
}),
22572257
),
2258-
PropertiesRow::with_override("Skew", WidgetOverride::Hidden),
2258+
PropertiesRow::with_override(
2259+
"Skew",
2260+
WidgetOverride::Vec2(Vec2InputSettings {
2261+
x: "X".to_string(),
2262+
y: "Y".to_string(),
2263+
unit: "°".to_string(),
2264+
..Default::default()
2265+
}),
2266+
),
22592267
PropertiesRow::with_override("Pivot", WidgetOverride::Hidden),
22602268
],
22612269
output_names: vec!["Data".to_string()],

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use crate::messages::portfolio::document::graph_operation::transform_utils;
2+
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
3+
14
use super::network_interface::NodeNetworkInterface;
25
use graph_craft::document::NodeId;
36
use graphene_core::renderer::ClickTarget;
@@ -17,7 +20,8 @@ use std::num::NonZeroU64;
1720
// TODO: it might be better to have a system that can query the state of the node network on demand.
1821
#[derive(Debug, Clone)]
1922
pub struct DocumentMetadata {
20-
pub upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
23+
pub upstream_footprints: HashMap<NodeId, Footprint>,
24+
pub local_transforms: HashMap<NodeId, DAffine2>,
2125
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
2226
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
2327
pub clip_targets: HashSet<NodeId>,
@@ -29,7 +33,8 @@ pub struct DocumentMetadata {
2933
impl Default for DocumentMetadata {
3034
fn default() -> Self {
3135
Self {
32-
upstream_transforms: HashMap::new(),
36+
upstream_footprints: HashMap::new(),
37+
local_transforms: HashMap::new(),
3338
structure: HashMap::new(),
3439
vector_modify: HashMap::new(),
3540
click_targets: HashMap::new(),
@@ -77,14 +82,27 @@ impl DocumentMetadata {
7782
}
7883

7984
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
80-
self.upstream_transforms
81-
.get(&layer.to_node())
82-
.map(|(footprint, transform)| footprint.transform * *transform)
83-
.unwrap_or(self.document_to_viewport)
85+
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
86+
let local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();
87+
88+
footprint * local_transform
89+
}
90+
91+
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
92+
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
93+
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
94+
95+
let transform = local_transform.unwrap_or_else(|| {
96+
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain("Transform", layer, network_interface);
97+
let transform_node = transform_node_id.and_then(|id| network_interface.document_node(&id, &[]));
98+
transform_node.map(|node| transform_utils::get_current_transform(node.inputs.as_slice())).unwrap_or_default()
99+
});
100+
101+
footprint * transform
84102
}
85103

86104
pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 {
87-
self.upstream_transforms.get(&node_id).copied().map(|(_, transform)| transform).unwrap_or(DAffine2::IDENTITY)
105+
self.local_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY)
88106
}
89107

90108
pub fn downstream_transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
@@ -96,10 +114,10 @@ impl DocumentMetadata {
96114
return self.transform_to_viewport(layer);
97115
}
98116

99-
self.upstream_transforms
117+
self.upstream_footprints
100118
.get(&layer.to_node())
101119
.copied()
102-
.map(|(footprint, _)| footprint.transform)
120+
.map(|footprint| footprint.transform)
103121
.unwrap_or_else(|| self.transform_to_viewport(layer))
104122
}
105123
}

editor/src/messages/portfolio/document/utility_types/network_interface.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -3244,14 +3244,16 @@ impl NodeNetworkInterface {
32443244

32453245
let nodes: HashSet<NodeId> = self.document_network().nodes.keys().cloned().collect::<HashSet<_>>();
32463246

3247-
self.document_metadata.upstream_transforms.retain(|node, _| nodes.contains(node));
3247+
self.document_metadata.upstream_footprints.retain(|node, _| nodes.contains(node));
3248+
self.document_metadata.local_transforms.retain(|node, _| nodes.contains(node));
32483249
self.document_metadata.vector_modify.retain(|node, _| nodes.contains(node));
32493250
self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
32503251
}
32513252

32523253
/// Update the cached transforms of the layers
3253-
pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
3254-
self.document_metadata.upstream_transforms = new_upstream_transforms;
3254+
pub fn update_transforms(&mut self, upstream_footprints: HashMap<NodeId, Footprint>, local_transforms: HashMap<NodeId, DAffine2>) {
3255+
self.document_metadata.upstream_footprints = upstream_footprints;
3256+
self.document_metadata.local_transforms = local_transforms;
32553257
}
32563258

32573259
/// Update the cached click targets of the layers

editor/src/messages/portfolio/document/utility_types/transformation.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::network_interface::NodeNetworkInterface;
22
use crate::consts::{ROTATE_INCREMENT, SCALE_INCREMENT};
3-
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
3+
use crate::messages::portfolio::document::graph_operation::transform_utils;
4+
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
45
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
56
use crate::messages::prelude::*;
67
use crate::messages::tool::common_functionality::graph_modification_utils;
@@ -54,17 +55,24 @@ impl OriginalTransforms {
5455
}
5556
}
5657

57-
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
58-
let document_metadata = network_interface.document_metadata();
58+
/// Gets the transform from the most downstream transform node
59+
fn get_layer_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DAffine2> {
60+
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain("Transform", layer, network_interface)?;
61+
62+
let document_node = network_interface.document_network().nodes.get(&transform_node_id)?;
63+
Some(transform_utils::get_current_transform(&document_node.inputs))
64+
}
5965

66+
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
6067
match self {
6168
OriginalTransforms::Layer(layer_map) => {
6269
layer_map.retain(|layer, _| selected.contains(layer));
6370
for &layer in selected {
6471
if layer == LayerNodeIdentifier::ROOT_PARENT {
6572
continue;
6673
}
67-
layer_map.entry(layer).or_insert_with(|| document_metadata.upstream_transform(layer.to_node()));
74+
75+
layer_map.entry(layer).or_insert_with(|| Self::get_layer_transform(layer, network_interface).unwrap_or_default());
6876
}
6977
}
7078
OriginalTransforms::Path(path_map) => {
@@ -550,7 +558,7 @@ impl<'a> Selected<'a> {
550558
.unwrap_or(DAffine2::IDENTITY);
551559

552560
if transform.matrix2.determinant().abs() <= f64::EPSILON {
553-
transform.matrix2 += DMat2::IDENTITY * 1e-4;
561+
transform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this?
554562
}
555563

556564
let bounds = self

0 commit comments

Comments
 (0)