Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo-artwork/isometric-fountain.graphite

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ignore = [
"RUSTSEC-2024-0388", # Unmaintained but still fully functional crate `derivative`
"RUSTSEC-2025-0007", # Unmaintained but still fully functional crate `ring`
"RUSTSEC-2024-0436", # Unmaintained but still fully functional crate `paste`
"RUSTSEC-2025-0014", # Unmaintained but still fully functional crate `humantime`
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
Expand Down
8 changes: 6 additions & 2 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,18 @@ impl Dispatcher {
};

let graphene_std::renderer::RenderMetadata {
footprints,
upstream_footprints: footprints,
local_transforms,
click_targets,
clip_targets,
} = render_metadata;

// Run these update state messages immediately
let messages = [
DocumentMessage::UpdateUpstreamTransforms { upstream_transforms: footprints },
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
},
DocumentMessage::UpdateClickTargets { click_targets },
DocumentMessage::UpdateClipTargets { clip_targets },
];
Expand Down
3 changes: 2 additions & 1 deletion editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ pub enum DocumentMessage {
ToggleOverlaysVisibility,
ToggleSnapping,
UpdateUpstreamTransforms {
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>,
},
UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
Expand Down
83 changes: 43 additions & 40 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1233,8 +1233,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::UpdateUpstreamTransforms { upstream_transforms } => {
self.network_interface.update_transforms(upstream_transforms);
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints,
local_transforms,
} => {
self.network_interface.update_transforms(upstream_footprints, local_transforms);
}
DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets
Expand Down Expand Up @@ -1634,6 +1637,44 @@ impl DocumentMessageHandler {
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
.or_else(|_| {
// TODO: Eventually remove this document upgrade code
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct OldDocumentMessageHandler {
// ============================================
// Fields that are saved in the document format
// ============================================
//
/// The node graph that generates this document's artwork.
/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
pub network: OldNodeNetwork,
/// List of the [`NodeId`]s that are currently selected by the user.
pub selected_nodes: SelectedNodes,
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
pub collapsed: CollapsedLayers,
/// The name of the document, which is displayed in the tab and title bar of the editor.
pub name: String,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
pub commit_hash: String,
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
pub document_ptz: PTZ,
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
pub document_mode: DocumentMode,
/// The current view mode that the user has set for rendering the document within the viewport.
/// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently.
pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
pub overlays_visible: bool,
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool,
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
pub graph_view_overlay_open: bool,
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
pub snapping_state: SnappingState,
}

serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
collapsed: old_message_handler.collapsed,
Expand Down Expand Up @@ -2639,41 +2680,3 @@ impl Iterator for ClickXRayIter<'_> {
None
}
}

// TODO: Eventually remove this document upgrade code
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct OldDocumentMessageHandler {
// ============================================
// Fields that are saved in the document format
// ============================================
//
/// The node graph that generates this document's artwork.
/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
pub network: OldNodeNetwork,
/// List of the [`NodeId`]s that are currently selected by the user.
pub selected_nodes: SelectedNodes,
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
pub collapsed: CollapsedLayers,
/// The name of the document, which is displayed in the tab and title bar of the editor.
pub name: String,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
pub commit_hash: String,
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
pub document_ptz: PTZ,
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
pub document_mode: DocumentMode,
/// The current view mode that the user has set for rendering the document within the viewport.
/// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently.
pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
pub overlays_visible: bool,
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool,
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
pub graph_view_overlay_open: bool,
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
pub snapping_state: SnappingState,
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,45 +237,52 @@ impl<'a> ModifyInputsContext<'a> {
}
})
}

/// 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.
/// 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.
/// Each dot after that represents an existing path node. If there is an existing upstream node, then it will always be returned first.
pub fn existing_node_id(&mut self, reference: &'static str, create_if_nonexistent: bool) -> Option<NodeId> {
pub fn existing_node_id(&mut self, reference_name: &'static str, create_if_nonexistent: bool) -> Option<NodeId> {
// Start from the layer node or export
let output_layer = self.get_output_layer()?;

let upstream = self
.network_interface
.upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
let existing_node_id = Self::locate_node_in_layer_chain(reference_name, output_layer, self.network_interface);

// Create a new node if the node does not exist and update its inputs
if create_if_nonexistent {
return existing_node_id.or_else(|| self.create_node(reference_name));
}

// Take until another layer node is found (but not the first layer node)
let mut existing_node_id = None;
for upstream_node in upstream.collect::<Vec<_>>() {
existing_node_id
}

/// 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.
/// 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.
/// 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.
pub fn locate_node_in_layer_chain(reference_name: &str, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);

// Look at all of the upstream nodes
for upstream_node in upstream {
// Check if this is the node we have been searching for.
if self
.network_interface
if network_interface
.reference(&upstream_node, &[])
.is_some_and(|node_reference| *node_reference == Some(reference.to_string()))
.is_some_and(|node_reference| *node_reference == Some(reference_name.to_string()))
{
existing_node_id = Some(upstream_node);
break;
}

let is_traversal_start = |node_id: NodeId| {
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))
};
if !network_interface.is_visible(&upstream_node, &[]) {
continue;
}

if !is_traversal_start(upstream_node) && (self.network_interface.is_layer(&upstream_node, &[])) {
break;
return Some(upstream_node);
}
}

// Create a new node if the node does not exist and update its inputs
if create_if_nonexistent {
return existing_node_id.or_else(|| self.create_node(reference));
// Take until another layer node is found (but not the first layer node)
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));
if !is_traversal_start(upstream_node) && (network_interface.is_layer(&upstream_node, &[])) {
return None;
}
}

existing_node_id
None
}

/// Create a new node inside the layer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2255,7 +2255,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override("Skew", WidgetOverride::Hidden),
PropertiesRow::with_override(
"Skew",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: "°".to_string(),
..Default::default()
}),
),
PropertiesRow::with_override("Pivot", WidgetOverride::Hidden),
],
output_names: vec!["Data".to_string()],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;

use super::network_interface::NodeNetworkInterface;
use graph_craft::document::NodeId;
use graphene_core::renderer::ClickTarget;
Expand All @@ -17,7 +20,8 @@ use std::num::NonZeroU64;
// TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)]
pub struct DocumentMetadata {
pub upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
Expand All @@ -29,7 +33,8 @@ pub struct DocumentMetadata {
impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_transforms: HashMap::new(),
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
structure: HashMap::new(),
vector_modify: HashMap::new(),
click_targets: HashMap::new(),
Expand Down Expand Up @@ -77,14 +82,27 @@ impl DocumentMetadata {
}

pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.upstream_transforms
.get(&layer.to_node())
.map(|(footprint, transform)| footprint.transform * *transform)
.unwrap_or(self.document_to_viewport)
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();

footprint * local_transform
}

pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let local_transform = self.local_transforms.get(&layer.to_node()).copied();

let transform = local_transform.unwrap_or_else(|| {
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain("Transform", layer, network_interface);
let transform_node = transform_node_id.and_then(|id| network_interface.document_node(&id, &[]));
transform_node.map(|node| transform_utils::get_current_transform(node.inputs.as_slice())).unwrap_or_default()
});

footprint * transform
}

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

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

self.upstream_transforms
self.upstream_footprints
.get(&layer.to_node())
.copied()
.map(|(footprint, _)| footprint.transform)
.map(|footprint| footprint.transform)
.unwrap_or_else(|| self.transform_to_viewport(layer))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3244,14 +3244,16 @@ impl NodeNetworkInterface {

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

self.document_metadata.upstream_transforms.retain(|node, _| nodes.contains(node));
self.document_metadata.upstream_footprints.retain(|node, _| nodes.contains(node));
self.document_metadata.local_transforms.retain(|node, _| nodes.contains(node));
self.document_metadata.vector_modify.retain(|node, _| nodes.contains(node));
self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
}

/// Update the cached transforms of the layers
pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
self.document_metadata.upstream_transforms = new_upstream_transforms;
pub fn update_transforms(&mut self, upstream_footprints: HashMap<NodeId, Footprint>, local_transforms: HashMap<NodeId, DAffine2>) {
self.document_metadata.upstream_footprints = upstream_footprints;
self.document_metadata.local_transforms = local_transforms;
}

/// Update the cached click targets of the layers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::network_interface::NodeNetworkInterface;
use crate::consts::{ROTATE_INCREMENT, SCALE_INCREMENT};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
Expand Down Expand Up @@ -54,17 +55,24 @@ impl OriginalTransforms {
}
}

pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
let document_metadata = network_interface.document_metadata();
/// Gets the transform from the most downstream transform node
fn get_layer_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DAffine2> {
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain("Transform", layer, network_interface)?;

let document_node = network_interface.document_network().nodes.get(&transform_node_id)?;
Some(transform_utils::get_current_transform(&document_node.inputs))
}

pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
match self {
OriginalTransforms::Layer(layer_map) => {
layer_map.retain(|layer, _| selected.contains(layer));
for &layer in selected {
if layer == LayerNodeIdentifier::ROOT_PARENT {
continue;
}
layer_map.entry(layer).or_insert_with(|| document_metadata.upstream_transform(layer.to_node()));

layer_map.entry(layer).or_insert_with(|| Self::get_layer_transform(layer, network_interface).unwrap_or_default());
}
}
OriginalTransforms::Path(path_map) => {
Expand Down Expand Up @@ -550,7 +558,7 @@ impl<'a> Selected<'a> {
.unwrap_or(DAffine2::IDENTITY);

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

let bounds = self
Expand Down
Loading