mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 06:58:12 +08:00
Add layer node chains, import/export edge connectors, and refactor graph editing to go thru a NodeNetworkInterface (#1794)
* WIP: NodeNetworkInterface * Organize ModifyInputsContext to use network interface * Improve ClickTarget and Position state * Rework ClickTarget state * Continue fixing NodeGraphMessageHandler * Restructure network_metadata * Final(?) NodeNetworkInterface struct * Final(??) NodeNetworkInterface * Final(???) NodeNetworkInterface. Separated persistent and transient data * Final NodeNetworkInterface data structure. Implemented all basic getters * Continue migrating functionality to network interface * Migrate all NodeGraphMessage's to use network interface * Fix all helper functions in NodeGraphMessageHandler * Move document metadata to network interface, remove various cached fields * Move all editor only NodeNetwork implementations to NodeNetworkInterface * Fix all DocumentNodeDefinitions * Rework and migrate GraphOperationMessages to network interface * Continue migration to NodeNetworkInterface * Save point before merging master * Fix all errors in network_interface * 850 -> 160 errors * Fix all errors :D * Render default document * Visualize click targets * merge conflicts * Cache transient metadata separately, store entire interface in document history * Start migration to storing selected nodes for each network * Remove selected nodes from document message handler * Move outward wires and all nodes bounding box to transient metadata * Fix connecting/disconnecting nodes * Layer stack organization for disconnecting/connecting nodes * Basic chain locking * Improve chain positioning * Add copy/pasting * Move upstream nodes on shift+drag * merge conflict fixes * Improve Graph.svelte code quality * Final improvements to Graph.svelte * Fix layer panel * Performance optimizations * Bug fixes and derived PTZ * Chain organization improvement and bug fixes * Bug fixes, remove all warnings * Automatic file upgrade * Final code review * Fix editor tests * Fix compile errors * Remove select tool intersection check when panning * WIP: Import/Exports * Fix JS issues * Finish simplified import/export UI * Import/Export viewport edge UI * Remove minimum y bound on import/export ports * Improve performance while panning graph * cargo fmt * Fix CI code build * Format the demo artwork graph with chains * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: dennis@kobert.dev <dennis@kobert.dev>
This commit is contained in:
co-authored by
Keavon Chambers
dennis@kobert.dev
parent
ea44d1440a
commit
0dbbabe73e
@@ -1,10 +1,13 @@
|
||||
use super::utility_types::misc::{OptionBoundsSnapping, OptionPointSnapping};
|
||||
use super::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
@@ -12,8 +15,6 @@ use graphene_core::Color;
|
||||
|
||||
use glam::DAffine2;
|
||||
|
||||
use super::utility_types::misc::{OptionBoundsSnapping, OptionPointSnapping};
|
||||
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
@@ -37,7 +38,7 @@ pub enum DocumentMessage {
|
||||
aggregate: AlignAggregate,
|
||||
},
|
||||
BackupDocument {
|
||||
network: NodeNetwork,
|
||||
network_interface: NodeNetworkInterface,
|
||||
},
|
||||
ClearArtboards,
|
||||
ClearLayersPanel,
|
||||
@@ -47,15 +48,18 @@ pub enum DocumentMessage {
|
||||
},
|
||||
CreateEmptyFolder,
|
||||
DebugPrintDocument,
|
||||
DeleteLayer {
|
||||
layer: LayerNodeIdentifier,
|
||||
},
|
||||
DeleteSelectedLayers,
|
||||
DeselectAllLayers,
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DuplicateSelectedLayers,
|
||||
EnterNestedNetwork {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ExitNestedNetwork {
|
||||
steps_back: usize,
|
||||
},
|
||||
FlipSelectedLayers {
|
||||
flip_axis: FlipAxis,
|
||||
},
|
||||
@@ -77,11 +81,14 @@ pub enum DocumentMessage {
|
||||
svg: String,
|
||||
transform: DAffine2,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
insert_index: usize,
|
||||
},
|
||||
MoveSelectedLayersTo {
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
insert_index: usize,
|
||||
},
|
||||
MoveSelectedLayersToGroup {
|
||||
parent: LayerNodeIdentifier,
|
||||
},
|
||||
NudgeSelectedLayers {
|
||||
delta_x: f64,
|
||||
@@ -103,7 +110,6 @@ pub enum DocumentMessage {
|
||||
},
|
||||
RenderRulers,
|
||||
RenderScrollbars,
|
||||
ResetTransform,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectedLayersLower,
|
||||
@@ -118,6 +124,9 @@ pub enum DocumentMessage {
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
},
|
||||
SetActivePanel {
|
||||
active_panel: PanelType,
|
||||
},
|
||||
SetBlendModeForSelectedLayers {
|
||||
blend_mode: BlendMode,
|
||||
},
|
||||
@@ -148,9 +157,10 @@ pub enum DocumentMessage {
|
||||
Undo,
|
||||
UndoFinished,
|
||||
UngroupSelectedLayers,
|
||||
UpdateDocumentTransform {
|
||||
transform: glam::DAffine2,
|
||||
UngroupLayer {
|
||||
layer: LayerNodeIdentifier,
|
||||
},
|
||||
PTZUpdate,
|
||||
ZoomCanvasTo100Percent,
|
||||
ZoomCanvasTo200Percent,
|
||||
ZoomCanvasToFitAll,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
||||
use super::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
@@ -11,59 +12,16 @@ use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::PointId;
|
||||
use graphene_core::vector::VectorModificationType;
|
||||
use graphene_core::{Artboard, Color};
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphOperationMessage {
|
||||
AddNodesAsChild {
|
||||
nodes: HashMap<NodeId, DocumentNode>,
|
||||
new_ids: HashMap<NodeId, NodeId>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
},
|
||||
CreateBooleanOperationNode {
|
||||
node_id: NodeId,
|
||||
operation: BooleanOperation,
|
||||
},
|
||||
DeleteLayer {
|
||||
layer: LayerNodeIdentifier,
|
||||
reconnect: bool,
|
||||
},
|
||||
DisconnectInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
},
|
||||
DisconnectNodeFromStack {
|
||||
node_id: NodeId,
|
||||
reconnect_to_sibling: bool,
|
||||
},
|
||||
FillSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
fill: Fill,
|
||||
},
|
||||
InsertNodeAtStackIndex {
|
||||
node_id: NodeId,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
InsertNodeBetween {
|
||||
// Post node
|
||||
post_node_id: NodeId,
|
||||
post_node_input_index: usize,
|
||||
// Inserted node
|
||||
insert_node_id: NodeId,
|
||||
insert_node_output_index: usize,
|
||||
insert_node_input_index: usize,
|
||||
// Pre node
|
||||
pre_node_id: NodeId,
|
||||
pre_node_output_index: usize,
|
||||
},
|
||||
MoveSelectedSiblingsToChild {
|
||||
new_parent: LayerNodeIdentifier,
|
||||
},
|
||||
OpacitySet {
|
||||
layer: LayerNodeIdentifier,
|
||||
opacity: f64,
|
||||
@@ -100,6 +58,9 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerNodeIdentifier,
|
||||
strokes: Vec<BrushStroke>,
|
||||
},
|
||||
SetUpstreamToChain {
|
||||
layer: LayerNodeIdentifier,
|
||||
},
|
||||
NewArtboard {
|
||||
id: NodeId,
|
||||
artboard: Artboard,
|
||||
@@ -108,20 +69,19 @@ pub enum GraphOperationMessage {
|
||||
id: NodeId,
|
||||
image_frame: ImageFrame<Color>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
insert_index: usize,
|
||||
},
|
||||
NewCustomLayer {
|
||||
id: NodeId,
|
||||
nodes: HashMap<NodeId, DocumentNode>,
|
||||
nodes: Vec<(NodeId, NodeTemplate)>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
alias: String,
|
||||
insert_index: usize,
|
||||
},
|
||||
NewVectorLayer {
|
||||
id: NodeId,
|
||||
subpaths: Vec<Subpath<PointId>>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
insert_index: usize,
|
||||
},
|
||||
NewTextLayer {
|
||||
id: NodeId,
|
||||
@@ -129,10 +89,10 @@ pub enum GraphOperationMessage {
|
||||
font: Font,
|
||||
size: f64,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
insert_index: usize,
|
||||
},
|
||||
ResizeArtboard {
|
||||
id: NodeId,
|
||||
layer: LayerNodeIdentifier,
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
},
|
||||
@@ -142,45 +102,6 @@ pub enum GraphOperationMessage {
|
||||
svg: String,
|
||||
transform: DAffine2,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
},
|
||||
ShiftUpstream {
|
||||
node_id: NodeId,
|
||||
shift: IVec2,
|
||||
shift_self: bool,
|
||||
},
|
||||
SetNodePosition {
|
||||
node_id: NodeId,
|
||||
position: IVec2,
|
||||
},
|
||||
SetName {
|
||||
layer: LayerNodeIdentifier,
|
||||
name: String,
|
||||
},
|
||||
SetNameImpl {
|
||||
layer: LayerNodeIdentifier,
|
||||
name: String,
|
||||
},
|
||||
SetNodeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
input: NodeInput,
|
||||
},
|
||||
ToggleSelectedVisibility,
|
||||
ToggleVisibility {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetVisibility {
|
||||
node_id: NodeId,
|
||||
visible: bool,
|
||||
},
|
||||
StartPreviewingWithoutRestore,
|
||||
ToggleSelectedLocked,
|
||||
ToggleLocked {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetLocked {
|
||||
node_id: NodeId,
|
||||
locked: bool,
|
||||
insert_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
+98
-567
@@ -1,24 +1,21 @@
|
||||
use super::transform_utils;
|
||||
use super::utility_types::ModifyInputsContext;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput};
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
|
||||
use graphene_core::Color;
|
||||
use graphene_std::vector::convert_usvg_path;
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
pub struct GraphOperationMessageData<'a> {
|
||||
pub document_network: &'a mut NodeNetwork,
|
||||
pub document_metadata: &'a mut DocumentMetadata,
|
||||
pub selected_nodes: &'a mut SelectedNodes,
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub collapsed: &'a mut CollapsedLayers,
|
||||
pub node_graph: &'a mut NodeGraphMessageHandler,
|
||||
}
|
||||
@@ -30,301 +27,26 @@ pub struct GraphOperationMessageHandler {}
|
||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
||||
let GraphOperationMessageData {
|
||||
document_network,
|
||||
document_metadata,
|
||||
selected_nodes,
|
||||
collapsed,
|
||||
node_graph,
|
||||
} = data;
|
||||
let network_interface = data.network_interface;
|
||||
|
||||
match message {
|
||||
GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index } => {
|
||||
let shift = document_network
|
||||
.get_root_node()
|
||||
.and_then(|root_node| {
|
||||
nodes.get(&root_node.id).and_then(|node| {
|
||||
if parent == LayerNodeIdentifier::ROOT_PARENT {
|
||||
return None;
|
||||
};
|
||||
let parent_node_id = parent.to_node();
|
||||
document_network
|
||||
.nodes
|
||||
.get(&parent_node_id)
|
||||
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
for (old_id, mut document_node) in nodes {
|
||||
// Shift copied node
|
||||
document_node.metadata.position += shift;
|
||||
|
||||
// Get the new, non-conflicting id
|
||||
let node_id = *new_ids.get(&old_id).unwrap();
|
||||
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
|
||||
document_node = document_node.map_ids(default_inputs, &new_ids);
|
||||
|
||||
// Insert node into network
|
||||
node_graph.insert_node(node_id, document_node, document_network, &Vec::new());
|
||||
}
|
||||
|
||||
let Some(new_layer_id) = new_ids.get(&NodeId(0)) else {
|
||||
error!("Could not get layer node when adding as child");
|
||||
return;
|
||||
};
|
||||
|
||||
let insert_index = if insert_index < 0 { 0 } else { insert_index as usize };
|
||||
let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
|
||||
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] });
|
||||
|
||||
match (downstream_node, upstream_node) {
|
||||
(Some(downstream_node), Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
|
||||
post_node_id: downstream_node,
|
||||
post_node_input_index: input_index,
|
||||
insert_node_output_index: 0,
|
||||
insert_node_id: *new_layer_id,
|
||||
insert_node_input_index: 0,
|
||||
pre_node_output_index: 0,
|
||||
pre_node_id: upstream_node,
|
||||
}),
|
||||
(Some(downstream_node), None) => responses.add(GraphOperationMessage::SetNodeInput {
|
||||
node_id: downstream_node,
|
||||
input_index,
|
||||
input: NodeInput::node(*new_layer_id, 0),
|
||||
}),
|
||||
(None, Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
|
||||
post_node_id: document_network.exports_metadata.0,
|
||||
post_node_input_index: 0,
|
||||
insert_node_output_index: 0,
|
||||
insert_node_id: *new_layer_id,
|
||||
insert_node_input_index: 0,
|
||||
pre_node_output_index: 0,
|
||||
pre_node_id: upstream_node,
|
||||
}),
|
||||
(None, None) => {
|
||||
if let Some(primary_export) = document_network.exports.get_mut(0) {
|
||||
*primary_export = NodeInput::node(*new_layer_id, 0)
|
||||
}
|
||||
}
|
||||
};
|
||||
responses.add(GraphOperationMessage::ShiftUpstream {
|
||||
node_id: *new_layer_id,
|
||||
shift: IVec2::new(0, 3),
|
||||
shift_self: true,
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::CreateBooleanOperationNode { node_id, operation } => {
|
||||
let new_boolean_operation_node = resolve_document_node_type("Boolean Operation")
|
||||
.expect("Failed to create a Boolean Operation node")
|
||||
.to_document_node_default_inputs(
|
||||
[
|
||||
Some(NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorData::empty()), true)),
|
||||
Some(NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorData::empty()), true)),
|
||||
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
||||
],
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
node_graph.insert_node(node_id, new_boolean_operation_node, document_network, &Vec::new());
|
||||
}
|
||||
GraphOperationMessage::DeleteLayer { layer, reconnect } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot delete ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
ModifyInputsContext::delete_nodes(node_graph, document_network, selected_nodes, vec![layer.to_node()], reconnect, responses, Vec::new());
|
||||
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
// Make sure to also update NodeGraphMessage::DisconnectInput when changing this
|
||||
GraphOperationMessage::DisconnectInput { node_id, input_index } => {
|
||||
let Some(existing_input) = document_network
|
||||
.nodes
|
||||
.get(&node_id)
|
||||
.map_or_else(|| document_network.exports.get(input_index), |node| node.inputs.get(input_index))
|
||||
else {
|
||||
warn!("Could not find input for {node_id} at index {input_index} when disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let tagged_value = TaggedValue::from_type(&ModifyInputsContext::get_input_type(document_network, &Vec::new(), node_id, &node_graph.resolved_types, input_index));
|
||||
|
||||
let mut input = NodeInput::value(tagged_value, true);
|
||||
if let NodeInput::Value { exposed, .. } = &mut input {
|
||||
*exposed = existing_input.is_exposed();
|
||||
}
|
||||
if node_id == document_network.exports_metadata.0 {
|
||||
// Since it is only possible to drag the solid line, there must be a root_node_to_restore
|
||||
if let Previewing::Yes { .. } = document_network.previewing {
|
||||
responses.add(GraphOperationMessage::StartPreviewingWithoutRestore);
|
||||
}
|
||||
// If there is no preview, then disconnect
|
||||
else {
|
||||
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
|
||||
}
|
||||
} else {
|
||||
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
|
||||
}
|
||||
if document_network.connected_to_output(node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
GraphOperationMessage::DisconnectNodeFromStack { node_id, reconnect_to_sibling } => {
|
||||
ModifyInputsContext::remove_references_from_network(node_graph, document_network, node_id, reconnect_to_sibling, &Vec::new());
|
||||
responses.add(GraphOperationMessage::DisconnectInput { node_id, input_index: 0 });
|
||||
}
|
||||
GraphOperationMessage::FillSet { layer, fill } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run FillSet on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.fill_set(fill);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::InsertNodeAtStackIndex { node_id, parent, insert_index } => {
|
||||
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
|
||||
|
||||
// `layer_to_move` should always correspond to a node.
|
||||
let Some(layer_to_move_node) = document_network.nodes.get(&node_id) else {
|
||||
log::error!("Layer node not found when inserting node {} at index {}", node_id, insert_index);
|
||||
return;
|
||||
};
|
||||
|
||||
// Move current layer to post node.
|
||||
let current_position = layer_to_move_node.metadata.position;
|
||||
let new_position = if let Some(post_node_id) = post_node_id {
|
||||
document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node").metadata.position
|
||||
} else if let Some(root_node) = document_network.get_root_node() {
|
||||
document_network.nodes.get(&root_node.id).expect("Root node id should always refer to a node").metadata.position + IVec2::new(8, -3)
|
||||
} else {
|
||||
document_network.exports_metadata.1
|
||||
};
|
||||
|
||||
// If moved to top of a layer stack, move to the left of the post node. If moved within a stack, move directly on the post node. The stack will be shifted down later.
|
||||
let offset_to_post_node = if insert_index == 0 {
|
||||
new_position - current_position - IVec2::new(8, 0)
|
||||
} else {
|
||||
new_position - current_position
|
||||
};
|
||||
|
||||
responses.add(GraphOperationMessage::ShiftUpstream {
|
||||
node_id,
|
||||
shift: offset_to_post_node,
|
||||
shift_self: true,
|
||||
});
|
||||
|
||||
match (post_node_id, pre_node_id) {
|
||||
(Some(post_node_id), Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
|
||||
post_node_id,
|
||||
post_node_input_index,
|
||||
insert_node_output_index: 0,
|
||||
insert_node_id: node_id,
|
||||
insert_node_input_index: 0,
|
||||
pre_node_output_index: 0,
|
||||
pre_node_id,
|
||||
}),
|
||||
(None, Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
|
||||
post_node_id: document_network.exports_metadata.0,
|
||||
post_node_input_index: 0,
|
||||
insert_node_output_index: 0,
|
||||
insert_node_id: node_id,
|
||||
insert_node_input_index: 0,
|
||||
pre_node_output_index: 0,
|
||||
pre_node_id,
|
||||
}),
|
||||
(Some(post_node_id), None) => responses.add(GraphOperationMessage::SetNodeInput {
|
||||
node_id: post_node_id,
|
||||
input_index: post_node_input_index,
|
||||
input: NodeInput::node(node_id, 0),
|
||||
}),
|
||||
(None, None) => {
|
||||
if let Some(primary_export) = document_network.exports.get_mut(0) {
|
||||
*primary_export = NodeInput::node(node_id, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift stack down, starting at the moved node.
|
||||
responses.add(GraphOperationMessage::ShiftUpstream {
|
||||
node_id,
|
||||
shift: IVec2::new(0, 3),
|
||||
shift_self: true,
|
||||
});
|
||||
}
|
||||
GraphOperationMessage::InsertNodeBetween {
|
||||
post_node_id,
|
||||
post_node_input_index,
|
||||
insert_node_output_index,
|
||||
insert_node_id,
|
||||
insert_node_input_index,
|
||||
pre_node_output_index,
|
||||
pre_node_id,
|
||||
} => {
|
||||
let post_node = document_network.nodes.get(&post_node_id);
|
||||
let Some((post_node_input_index, _)) = post_node
|
||||
.map_or(&document_network.exports, |post_node| &post_node.inputs)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|input| input.1.is_exposed())
|
||||
.nth(post_node_input_index)
|
||||
else {
|
||||
error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
|
||||
return;
|
||||
};
|
||||
let Some(insert_node) = document_network.nodes.get(&insert_node_id) else {
|
||||
error!("Insert node not found");
|
||||
return;
|
||||
};
|
||||
let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else {
|
||||
error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}");
|
||||
return;
|
||||
};
|
||||
|
||||
let post_input = NodeInput::node(insert_node_id, insert_node_output_index);
|
||||
responses.add(GraphOperationMessage::SetNodeInput {
|
||||
node_id: post_node_id,
|
||||
input_index: post_node_input_index,
|
||||
input: post_input,
|
||||
});
|
||||
|
||||
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index);
|
||||
responses.add(GraphOperationMessage::SetNodeInput {
|
||||
node_id: insert_node_id,
|
||||
input_index: insert_node_input_index,
|
||||
input: insert_input,
|
||||
});
|
||||
}
|
||||
GraphOperationMessage::OpacitySet { layer, opacity } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run OpacitySet on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.opacity_set(opacity);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run BlendModeSet on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.blend_mode_set(blend_mode);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run StrokeSet on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.stroke_set(stroke);
|
||||
}
|
||||
}
|
||||
@@ -334,12 +56,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
transform_in,
|
||||
skip_rerender,
|
||||
} => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run TransformChange on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
let parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
|
||||
}
|
||||
}
|
||||
@@ -349,14 +67,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
transform_in,
|
||||
skip_rerender,
|
||||
} => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run TransformSet on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||
|
||||
let current_transform = Some(document_metadata.transform_to_viewport(layer));
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
let parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer);
|
||||
let current_transform = Some(network_interface.document_metadata().transform_to_viewport(layer));
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender);
|
||||
}
|
||||
}
|
||||
@@ -365,7 +78,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.pivot_set(pivot);
|
||||
}
|
||||
}
|
||||
@@ -374,96 +87,51 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.vector_modify(modification_type);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Brush { layer, strokes } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run Brush on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.brush_modify(strokes);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => {
|
||||
let Some(group_parent) = new_parent.parent(document_metadata) else {
|
||||
log::error!("Could not find parent for layer {:?}", new_parent);
|
||||
GraphOperationMessage::SetUpstreamToChain { layer } => {
|
||||
let Some(first_chain_node) = network_interface
|
||||
.upstream_flow_back_from_nodes(
|
||||
vec![layer.to_node()],
|
||||
&[],
|
||||
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
|
||||
)
|
||||
.nth(1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer
|
||||
let mut selected_siblings = Vec::new();
|
||||
|
||||
// Skip over horizontal non layer node chain that feeds into parent
|
||||
let Some(mut current_stack_node_id) = group_parent.first_child(document_metadata).map(|current_stack_node| current_stack_node.to_node()) else {
|
||||
log::error!("Folder should always have child");
|
||||
return;
|
||||
};
|
||||
let current_stack_node_id = &mut current_stack_node_id;
|
||||
|
||||
loop {
|
||||
let mut current_stack_node = document_network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node");
|
||||
|
||||
// Check if the current stack node is a selected layer
|
||||
if selected_nodes
|
||||
.selected_layers(document_metadata)
|
||||
.any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id)
|
||||
{
|
||||
selected_siblings.push(*current_stack_node_id);
|
||||
|
||||
// Push all non layer sibling nodes directly upstream of the selected layer
|
||||
loop {
|
||||
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.first() else { break };
|
||||
|
||||
let next_node = document_network.nodes.get(node_id).expect("Stack node id should always be a node");
|
||||
|
||||
// If the next node is a layer, immediately break and leave current stack node as the non layer node
|
||||
if next_node.is_layer {
|
||||
break;
|
||||
}
|
||||
|
||||
*current_stack_node_id = *node_id;
|
||||
current_stack_node = next_node;
|
||||
|
||||
selected_siblings.push(*current_stack_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Get next node
|
||||
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.first() else { break };
|
||||
*current_stack_node_id = *node_id;
|
||||
}
|
||||
|
||||
// Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec
|
||||
for node_to_move in selected_siblings.iter().rev() {
|
||||
// Disconnect node, then reconnect as new child
|
||||
responses.add(GraphOperationMessage::DisconnectNodeFromStack {
|
||||
node_id: *node_to_move,
|
||||
reconnect_to_sibling: true,
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
|
||||
node_id: *node_to_move,
|
||||
parent: new_parent,
|
||||
insert_index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let Some(most_upstream_sibling) = selected_siblings.last() else {
|
||||
return;
|
||||
};
|
||||
responses.add(GraphOperationMessage::DisconnectInput {
|
||||
node_id: *most_upstream_sibling,
|
||||
input_index: 0,
|
||||
});
|
||||
network_interface.force_set_upstream_to_chain(&first_chain_node, &[]);
|
||||
}
|
||||
GraphOperationMessage::NewArtboard { id, artboard } => {
|
||||
if let Some(artboard_id) = ModifyInputsContext::create_artboard(node_graph, document_network, id, artboard) {
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![artboard_id] });
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
|
||||
let artboard_layer = modify_inputs.create_artboard(id, artboard);
|
||||
network_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);
|
||||
|
||||
// If there is a non artboard feeding into the primary input of the artboard, move it to the secondary input
|
||||
let Some(artboard) = network_interface.network(&[]).unwrap().nodes.get(&id) else {
|
||||
log::error!("Artboard not created");
|
||||
return;
|
||||
};
|
||||
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
|
||||
if let NodeInput::Node { node_id, .. } = &primary_input {
|
||||
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
|
||||
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]);
|
||||
} else {
|
||||
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
|
||||
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
|
||||
}
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::NewBitmapLayer {
|
||||
id,
|
||||
@@ -471,71 +139,38 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
parent,
|
||||
insert_index,
|
||||
} => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
|
||||
ModifyInputsContext::insert_image_data(node_graph, document_network, image_frame, layer, responses);
|
||||
}
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.insert_image_data(image_frame, layer);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::NewCustomLayer {
|
||||
id,
|
||||
nodes,
|
||||
parent,
|
||||
insert_index,
|
||||
alias,
|
||||
} => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
|
||||
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
|
||||
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
|
||||
if !nodes.is_empty() {
|
||||
// Add the nodes to the network
|
||||
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
||||
// Since all the new nodes are already connected, just connect the input of the layer to first new node
|
||||
let first_new_node_id = new_ids[&NodeId(0)];
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
|
||||
|
||||
if let Some(node) = modify_inputs.document_network.nodes.get_mut(&id) {
|
||||
node.alias.clone_from(&alias);
|
||||
}
|
||||
|
||||
let shift = nodes
|
||||
.get(&NodeId(0))
|
||||
.and_then(|node| {
|
||||
modify_inputs
|
||||
.document_network
|
||||
.nodes
|
||||
.get(&layer)
|
||||
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
for (old_id, mut document_node) in nodes {
|
||||
// Shift copied node
|
||||
document_node.metadata.position += shift;
|
||||
|
||||
// Get the new, non-conflicting id
|
||||
let node_id = *new_ids.get(&old_id).unwrap();
|
||||
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
|
||||
document_node = document_node.map_ids(default_inputs, &new_ids);
|
||||
|
||||
// Insert node into network
|
||||
node_graph.insert_node(node_id, document_node, document_network, &Vec::new());
|
||||
node_graph.update_click_target(node_id, document_network, Vec::new());
|
||||
}
|
||||
|
||||
if let Some(layer_node) = document_network.nodes.get_mut(&layer) {
|
||||
if let Some(&input) = new_ids.get(&NodeId(0)) {
|
||||
layer_node.inputs[1] = NodeInput::node(input, 0);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
error!("Creating new custom layer failed");
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(layer.to_node(), 1),
|
||||
input: NodeInput::node(first_new_node_id, 0),
|
||||
});
|
||||
}
|
||||
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
// Move the layer and all nodes to the correct position in the network
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::NewTextLayer {
|
||||
id,
|
||||
@@ -545,22 +180,25 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
parent,
|
||||
insert_index,
|
||||
} => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
|
||||
modify_inputs.insert_text(text, font, size, layer);
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.insert_text(text, font, size, layer);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) {
|
||||
GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.resize_artboard(location, dimensions);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::ClearArtboards => {
|
||||
for &artboard in document_metadata.all_artboards() {
|
||||
responses.add(GraphOperationMessage::DeleteLayer { layer: artboard, reconnect: true });
|
||||
for artboard in network_interface.all_artboards() {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![artboard.to_node()],
|
||||
reconnect: false,
|
||||
});
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
// TODO: Replace deleted artboards with merge nodes
|
||||
}
|
||||
GraphOperationMessage::NewSvg {
|
||||
id,
|
||||
@@ -580,112 +218,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
|
||||
import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root().clone())), transform, id, parent, insert_index);
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
}
|
||||
GraphOperationMessage::SetNodePosition { node_id, position } => {
|
||||
let Some(node) = document_network.nodes.get_mut(&node_id) else {
|
||||
log::error!("Failed to find node {node_id} when setting position");
|
||||
return;
|
||||
};
|
||||
node.metadata.position = position;
|
||||
node_graph.update_click_target(node_id, document_network, Vec::new());
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
}
|
||||
GraphOperationMessage::SetName { layer, name } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::SetNameImpl { layer, name } => {
|
||||
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
|
||||
node.alias = name;
|
||||
if let Some(node_metadata) = node_graph.node_metadata.get_mut(&layer.to_node()) {
|
||||
node_metadata.layer_width = Some(NodeGraphMessageHandler::layer_width_cells(node));
|
||||
};
|
||||
node_graph.update_click_target(layer.to_node(), document_network, Vec::new());
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::SetNodeInput { node_id, input_index, input } => {
|
||||
if ModifyInputsContext::set_input(node_graph, document_network, &Vec::new(), node_id, input_index, input, true) {
|
||||
load_network_structure(document_network, document_metadata, collapsed);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::ShiftUpstream { node_id, shift, shift_self } => {
|
||||
ModifyInputsContext::shift_upstream(node_graph, document_network, &Vec::new(), node_id, shift, shift_self);
|
||||
}
|
||||
GraphOperationMessage::ToggleSelectedVisibility => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
|
||||
let visible = !selected_nodes.selected_layers(document_metadata).all(|layer| document_metadata.node_is_visible(layer.to_node()));
|
||||
|
||||
for layer in selected_nodes.selected_layers(document_metadata) {
|
||||
responses.add(GraphOperationMessage::SetVisibility { node_id: layer.to_node(), visible });
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::ToggleVisibility { node_id } => {
|
||||
let visible = !document_metadata.node_is_visible(node_id);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::SetVisibility { node_id, visible });
|
||||
}
|
||||
GraphOperationMessage::SetVisibility { node_id, visible } => {
|
||||
// Set what we determined shall be the visibility of the node
|
||||
let Some(node) = document_network.nodes.get_mut(&node_id) else {
|
||||
log::error!("Could not get node {:?} in GraphOperationMessage::SetVisibility", node_id);
|
||||
return;
|
||||
};
|
||||
node.visible = visible;
|
||||
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if document_network.connected_to_output(node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
document_metadata.load_structure(document_network);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
GraphOperationMessage::StartPreviewingWithoutRestore => {
|
||||
document_network.start_previewing_without_restore();
|
||||
}
|
||||
GraphOperationMessage::ToggleSelectedLocked => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// If any of the selected nodes are locked, show them all. Otherwise, hide them all.
|
||||
let locked = !selected_nodes.selected_layers(document_metadata).all(|layer| document_metadata.node_is_locked(layer.to_node()));
|
||||
|
||||
for layer in selected_nodes.selected_layers(document_metadata) {
|
||||
responses.add(GraphOperationMessage::SetLocked { node_id: layer.to_node(), locked });
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::ToggleLocked { node_id } => {
|
||||
let Some(node) = document_network.nodes.get(&node_id) else {
|
||||
log::error!("Cannot get node {:?} in GraphOperationMessage::ToggleLocked", node_id);
|
||||
return;
|
||||
};
|
||||
|
||||
let locked = !node.locked;
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::SetLocked { node_id, locked });
|
||||
}
|
||||
GraphOperationMessage::SetLocked { node_id, locked } => {
|
||||
let Some(node) = document_network.nodes.get_mut(&node_id) else { return };
|
||||
node.locked = locked;
|
||||
|
||||
if document_network.connected_to_output(node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
document_metadata.load_structure(document_network);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -695,11 +230,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, collapsed: &mut CollapsedLayers) {
|
||||
document_metadata.load_structure(document_network);
|
||||
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer));
|
||||
}
|
||||
|
||||
fn usvg_color(c: usvg::Color, a: f32) -> Color {
|
||||
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
|
||||
}
|
||||
@@ -708,15 +238,13 @@ fn usvg_transform(c: usvg::Transform) -> DAffine2 {
|
||||
DAffine2::from_cols_array(&[c.sx as f64, c.ky as f64, c.kx as f64, c.sy as f64, c.tx as f64, c.ty as f64])
|
||||
}
|
||||
|
||||
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: isize) {
|
||||
let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) else {
|
||||
return;
|
||||
};
|
||||
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: usize) {
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.layer_node = Some(layer);
|
||||
match node {
|
||||
usvg::Node::Group(group) => {
|
||||
for child in group.children() {
|
||||
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), LayerNodeIdentifier::new_unchecked(layer), -1);
|
||||
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), layer, 0);
|
||||
}
|
||||
modify_inputs.layer_node = Some(layer);
|
||||
}
|
||||
@@ -725,9 +253,12 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
|
||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
|
||||
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
|
||||
transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform()));
|
||||
});
|
||||
modify_inputs.network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
|
||||
if let Some(transform_node_id) = modify_inputs.get_existing_node_id("Transform") {
|
||||
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));
|
||||
}
|
||||
|
||||
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
apply_usvg_fill(path.fill(), modify_inputs, transform * usvg_transform(node.abs_transform()), bounds_transform);
|
||||
apply_usvg_stroke(path.stroke(), modify_inputs);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::{value::TaggedValue, NodeInput};
|
||||
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -30,13 +32,13 @@ pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64
|
||||
}
|
||||
|
||||
/// Update the inputs of the transform node to match a new transform
|
||||
pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) {
|
||||
pub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: &NodeId, transform: DAffine2) {
|
||||
let (scale, angle, translation, shear) = compute_scale_angle_translation_shear(transform);
|
||||
|
||||
inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F64(angle), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::DVec2(shear), false);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(translation), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(angle), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::DVec2(scale), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::DVec2(shear), false), &[]);
|
||||
}
|
||||
|
||||
// TODO: This should be extracted from the graph at the location of the transform node.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::portfolio::document::navigation::utility_types::NavigationOperation;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
|
||||
use crate::messages::portfolio::document::utility_types::misc::PTZ;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
@@ -16,14 +16,12 @@ use graph_craft::document::NodeId;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
pub struct NavigationMessageData<'a> {
|
||||
pub metadata: &'a DocumentMetadata,
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub breadcrumb_network_path: &'a [NodeId],
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub selection_bounds: Option<[DVec2; 2]>,
|
||||
pub document_ptz: &'a mut PTZ,
|
||||
pub node_graph_ptz: &'a mut HashMap<Vec<NodeId>, PTZ>,
|
||||
pub graph_view_overlay_open: bool,
|
||||
pub node_graph_handler: &'a NodeGraphMessageHandler,
|
||||
pub node_graph_to_viewport: &'a DAffine2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
@@ -36,24 +34,46 @@ pub struct NavigationMessageHandler {
|
||||
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
||||
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
||||
let NavigationMessageData {
|
||||
metadata,
|
||||
network_interface,
|
||||
breadcrumb_network_path,
|
||||
ipp,
|
||||
selection_bounds,
|
||||
document_ptz,
|
||||
node_graph_ptz,
|
||||
graph_view_overlay_open,
|
||||
node_graph_handler,
|
||||
node_graph_to_viewport,
|
||||
} = data;
|
||||
let ptz = if !graph_view_overlay_open {
|
||||
document_ptz
|
||||
} else {
|
||||
node_graph_ptz.entry(node_graph_handler.network.clone()).or_insert(PTZ::default())
|
||||
|
||||
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
|
||||
if !graph_view_overlay_open {
|
||||
Some(document_ptz)
|
||||
} else {
|
||||
let network_metadata = network_interface.network_metadata(breadcrumb_network_path)?;
|
||||
Some(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ptz_mut<'a>(document_ptz: &'a mut PTZ, network_interface: &'a mut NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a mut PTZ> {
|
||||
if !graph_view_overlay_open {
|
||||
Some(document_ptz)
|
||||
} else {
|
||||
let Some(node_graph_ptz) = network_interface.node_graph_ptz_mut(breadcrumb_network_path) else {
|
||||
log::error!("Could not get node graph PTZ in NavigationMessageHandler process_message");
|
||||
return None;
|
||||
};
|
||||
Some(node_graph_ptz)
|
||||
}
|
||||
}
|
||||
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get PTZ in NavigationMessageHandler process_message");
|
||||
return;
|
||||
};
|
||||
let old_zoom = ptz.zoom();
|
||||
|
||||
match message {
|
||||
NavigationMessage::BeginCanvasPan => {
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
|
||||
|
||||
responses.add(FrontendMessage::UpdateInputHints {
|
||||
@@ -64,6 +84,9 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
self.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan };
|
||||
}
|
||||
NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => {
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
// If the node graph is open, prevent tilt and instead start panning
|
||||
if graph_view_overlay_open {
|
||||
responses.add(NavigationMessage::BeginCanvasPan);
|
||||
@@ -94,6 +117,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
}
|
||||
}
|
||||
NavigationMessage::BeginCanvasZoom => {
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
|
||||
responses.add(FrontendMessage::UpdateInputHints {
|
||||
hint_data: HintData(vec![
|
||||
@@ -117,24 +144,27 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
NavigationMessage::CanvasPan { delta } => {
|
||||
let transformed_delta = if !graph_view_overlay_open {
|
||||
metadata.document_to_viewport.inverse().transform_vector2(delta)
|
||||
} else {
|
||||
node_graph_to_viewport.inverse().transform_vector2(delta)
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get PTZ in CanvasPan");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasPanByViewportFraction { delta } => {
|
||||
let transformed_delta = if !graph_view_overlay_open {
|
||||
metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
|
||||
} else {
|
||||
node_graph_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => {
|
||||
let delta = match use_y_as_x {
|
||||
@@ -144,16 +174,28 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
responses.add(NavigationMessage::CanvasPan { delta });
|
||||
}
|
||||
NavigationMessage::CanvasTiltResetAndZoomTo100Percent => {
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent");
|
||||
return;
|
||||
};
|
||||
ptz.tilt = 0.;
|
||||
ptz.set_zoom(1.);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasTiltSet { angle_radians } => {
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in CanvasTiltSet");
|
||||
return;
|
||||
};
|
||||
ptz.tilt = angle_radians;
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasZoomDecrease { center_on_mouse } => {
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom()).unwrap_or(&ptz.zoom());
|
||||
if center_on_mouse {
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
@@ -161,6 +203,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });
|
||||
}
|
||||
NavigationMessage::CanvasZoomIncrease { center_on_mouse } => {
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom()).unwrap_or(&ptz.zoom());
|
||||
if center_on_mouse {
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
@@ -175,10 +221,14 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
}
|
||||
let document_bounds = if !graph_view_overlay_open {
|
||||
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
|
||||
metadata.document_bounds_viewport_space()
|
||||
network_interface.document_metadata().document_bounds_viewport_space()
|
||||
} else {
|
||||
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
|
||||
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
|
||||
};
|
||||
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, ipp);
|
||||
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
|
||||
@@ -189,17 +239,25 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
NavigationMessage::CanvasZoomSet { zoom_factor } => {
|
||||
let document_bounds = if !graph_view_overlay_open {
|
||||
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
|
||||
metadata.document_bounds_viewport_space()
|
||||
network_interface.document_metadata().document_bounds_viewport_space()
|
||||
} else {
|
||||
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
|
||||
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
|
||||
};
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in CanvasZoomSet");
|
||||
return;
|
||||
};
|
||||
let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp);
|
||||
ptz.set_zoom(zoom);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::EndCanvasPTZ { abort_transform } => {
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in EndCanvasPTZ");
|
||||
return;
|
||||
};
|
||||
// If an abort was requested, reset the active PTZ value to its original state
|
||||
if abort_transform && self.navigation_operation != NavigationOperation::None {
|
||||
match self.navigation_operation {
|
||||
@@ -215,7 +273,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
}
|
||||
}
|
||||
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
|
||||
// Final chance to apply snapping if the key was pressed during this final frame
|
||||
@@ -248,18 +306,20 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = (if graph_view_overlay_open { *node_graph_to_viewport } else { metadata.document_to_viewport }).inverse();
|
||||
let (v1, v2) = (transform.transform_point2(DVec2::ZERO), transform.transform_point2(ipp.viewport_bounds.size()));
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
|
||||
let v1 = document_to_viewport.inverse().transform_point2(DVec2::ZERO);
|
||||
let v2 = document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size());
|
||||
|
||||
let center = ((v2 + v1) - (pos2 + pos1)) / 2.;
|
||||
let size = (v2 - v1) / diagonal;
|
||||
let new_scale = size.min_element();
|
||||
|
||||
let viewport_change = if !graph_view_overlay_open {
|
||||
metadata.document_to_viewport.transform_vector2(center)
|
||||
} else {
|
||||
node_graph_to_viewport.transform_vector2(center)
|
||||
};
|
||||
let viewport_change = document_to_viewport.transform_vector2(center);
|
||||
|
||||
// Only change the pan if the change will be visible in the viewport
|
||||
if viewport_change.x.abs() > 0.5 || viewport_change.y.abs() > 0.5 {
|
||||
@@ -275,17 +335,17 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::FitViewportToSelection => {
|
||||
if let Some(bounds) = selection_bounds {
|
||||
let transform = if !graph_view_overlay_open {
|
||||
metadata.document_to_viewport.inverse()
|
||||
} else {
|
||||
node_graph_to_viewport.inverse()
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get node graph PTZ in FitViewportToSelection");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
responses.add(NavigationMessage::FitViewportToBounds {
|
||||
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
|
||||
bounds: [document_to_viewport.inverse().transform_point2(bounds[0]), document_to_viewport.inverse().transform_point2(bounds[1])],
|
||||
prevent_zoom_past_100: false,
|
||||
})
|
||||
}
|
||||
@@ -310,6 +370,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
|
||||
tilt_raw_not_snapped + angle
|
||||
};
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in Tilt");
|
||||
return;
|
||||
};
|
||||
ptz.tilt = self.snapped_tilt(tilt_raw_not_snapped);
|
||||
|
||||
let snap = ipp.keyboard.get(snap as usize);
|
||||
@@ -334,13 +398,17 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||
|
||||
let document_bounds = if !graph_view_overlay_open {
|
||||
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
|
||||
metadata.document_bounds_viewport_space()
|
||||
network_interface.document_metadata().document_bounds_viewport_space()
|
||||
} else {
|
||||
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
|
||||
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
|
||||
};
|
||||
|
||||
updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp)
|
||||
};
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in Zoom");
|
||||
return;
|
||||
};
|
||||
ptz.set_zoom(self.snapped_zoom(zoom_raw_not_snapped));
|
||||
|
||||
let snap = ipp.keyboard.get(snap as usize);
|
||||
@@ -413,7 +481,11 @@ impl NavigationMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_offset_transform(&self, viewport_center: DVec2, pan: DVec2, tilt: f64, zoom: f64) -> DAffine2 {
|
||||
pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {
|
||||
let pan = ptz.pan;
|
||||
let tilt = ptz.tilt;
|
||||
let zoom = ptz.zoom();
|
||||
|
||||
let scaled_center = viewport_center / self.snapped_zoom(zoom);
|
||||
|
||||
// Try to avoid fractional coordinates to reduce anti aliasing.
|
||||
@@ -428,11 +500,6 @@ impl NavigationMessageHandler {
|
||||
scale_transform * offset_transform * angle_transform * translation_transform
|
||||
}
|
||||
|
||||
fn create_document_transform(&self, viewport_center: DVec2, ptz: &PTZ, responses: &mut VecDeque<Message>) {
|
||||
let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom());
|
||||
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
|
||||
}
|
||||
|
||||
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,34 @@
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
|
||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NodeGraphMessage {
|
||||
// Messages
|
||||
AddNodes {
|
||||
nodes: Vec<(NodeId, NodeTemplate)>,
|
||||
new_ids: HashMap<NodeId, NodeId>,
|
||||
},
|
||||
Init,
|
||||
SelectedNodesUpdated,
|
||||
ConnectNodesByWire {
|
||||
output_node: NodeId,
|
||||
output_node_connector_index: usize,
|
||||
input_node: NodeId,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
Copy,
|
||||
CloseCreateNodeMenu,
|
||||
CreateNode {
|
||||
CreateNodeFromContextMenu {
|
||||
node_id: Option<NodeId>,
|
||||
node_type: String,
|
||||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
CreateWire {
|
||||
output_connector: OutputConnector,
|
||||
input_connector: InputConnector,
|
||||
},
|
||||
Cut,
|
||||
DeleteNodes {
|
||||
node_ids: Vec<NodeId>,
|
||||
@@ -35,17 +38,10 @@ pub enum NodeGraphMessage {
|
||||
reconnect: bool,
|
||||
},
|
||||
DisconnectInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
input_connector: InputConnector,
|
||||
},
|
||||
EnterNestedNetwork,
|
||||
DuplicateSelectedNodes,
|
||||
EnforceLayerHasNoMultiParams {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ExitNestedNetwork {
|
||||
steps_back: usize,
|
||||
},
|
||||
ExposeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
@@ -53,21 +49,17 @@ pub enum NodeGraphMessage {
|
||||
},
|
||||
InsertNode {
|
||||
node_id: NodeId,
|
||||
document_node: DocumentNode,
|
||||
node_template: NodeTemplate,
|
||||
},
|
||||
InsertNodeBetween {
|
||||
post_node_id: NodeId,
|
||||
post_node_input_index: usize,
|
||||
insert_node_output_index: usize,
|
||||
insert_node_id: NodeId,
|
||||
node_id: NodeId,
|
||||
input_connector: InputConnector,
|
||||
insert_node_input_index: usize,
|
||||
pre_node_output_index: usize,
|
||||
pre_node_id: NodeId,
|
||||
},
|
||||
MoveSelectedNodes {
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
move_upstream: bool,
|
||||
MoveLayerToStack {
|
||||
layer: LayerNodeIdentifier,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
PasteNodes {
|
||||
serialized_nodes: String,
|
||||
@@ -87,6 +79,7 @@ pub enum NodeGraphMessage {
|
||||
},
|
||||
PrintSelectedNodeCoordinates,
|
||||
RunDocumentGraph,
|
||||
ForceRunDocumentGraph,
|
||||
SelectedNodesAdd {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
@@ -96,48 +89,35 @@ pub enum NodeGraphMessage {
|
||||
SelectedNodesSet {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
SendClickTargets,
|
||||
EndSendClickTargets,
|
||||
SendGraph,
|
||||
SetInputValue {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
value: TaggedValue,
|
||||
},
|
||||
SetNodeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
SetInput {
|
||||
input_connector: InputConnector,
|
||||
input: NodeInput,
|
||||
},
|
||||
SetQualifiedInputValue {
|
||||
SetDisplayName {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
value: TaggedValue,
|
||||
alias: String,
|
||||
},
|
||||
/// Move all the downstream nodes to the right in the graph to allow space for a newly inserted node
|
||||
ShiftNode {
|
||||
SetDisplayNameImpl {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetVisibility {
|
||||
node_id: NodeId,
|
||||
visible: bool,
|
||||
},
|
||||
SetLocked {
|
||||
node_id: NodeId,
|
||||
locked: bool,
|
||||
},
|
||||
SetName {
|
||||
node_id: NodeId,
|
||||
name: String,
|
||||
},
|
||||
SetNameImpl {
|
||||
node_id: NodeId,
|
||||
name: String,
|
||||
alias: String,
|
||||
},
|
||||
SetToNodeOrLayer {
|
||||
node_id: NodeId,
|
||||
is_layer: bool,
|
||||
},
|
||||
StartPreviewingWithoutRestore {
|
||||
node_id: NodeId,
|
||||
ShiftNodes {
|
||||
node_ids: Vec<NodeId>,
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
move_upstream: bool,
|
||||
},
|
||||
TogglePreview {
|
||||
node_id: NodeId,
|
||||
@@ -146,10 +126,28 @@ pub enum NodeGraphMessage {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ToggleSelectedAsLayersOrNodes,
|
||||
ToggleSelectedLocked,
|
||||
ToggleLocked {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetLocked {
|
||||
node_id: NodeId,
|
||||
locked: bool,
|
||||
},
|
||||
ToggleSelectedVisibility,
|
||||
ToggleVisibility {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetVisibility {
|
||||
node_id: NodeId,
|
||||
visible: bool,
|
||||
},
|
||||
SetLockedOrVisibilitySideEffects {
|
||||
node_ids: Vec<NodeId>,
|
||||
},
|
||||
UpdateEdges,
|
||||
UpdateBoxSelection,
|
||||
UpdateLayerPanel,
|
||||
UpdateNewNodeGraph,
|
||||
UpdateTypes {
|
||||
#[serde(skip)]
|
||||
@@ -157,4 +155,7 @@ pub enum NodeGraphMessage {
|
||||
#[serde(skip)]
|
||||
node_graph_errors: GraphErrors,
|
||||
},
|
||||
UpdateActionButtons,
|
||||
UpdateInSelectedNetwork,
|
||||
SendSelectedNodes,
|
||||
}
|
||||
|
||||
+920
-1880
File diff suppressed because it is too large
Load Diff
@@ -73,11 +73,7 @@ fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
||||
|
||||
fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!(
|
||||
"A widget named '{name}' for node {} (alias '{}') failed to be built because its node's input index {index} is invalid.",
|
||||
document_node.name,
|
||||
document_node.alias
|
||||
);
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let mut widgets = vec![expose_widget(node_id, index, data_type, input.is_exposed()), TextLabel::new(name).widget_holder()];
|
||||
@@ -1632,9 +1628,17 @@ pub fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
}
|
||||
|
||||
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let imaginate_node = [context.nested_path, &[node_id]].concat();
|
||||
let imaginate_node = [context.selection_network_path, &[node_id]].concat();
|
||||
|
||||
let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
let resolve_input = |name: &str| {
|
||||
IMAGINATE_NODE
|
||||
.default_node_template()
|
||||
.persistent_node_metadata
|
||||
.input_names
|
||||
.iter()
|
||||
.position(|input| input == name)
|
||||
.unwrap_or_else(|| panic!("Input {name} not found"))
|
||||
};
|
||||
let seed_index = resolve_input("Seed");
|
||||
let resolution_index = resolve_input("Resolution");
|
||||
let samples_index = resolve_input("Samples");
|
||||
@@ -1830,7 +1834,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
let image_size = context
|
||||
.executor
|
||||
.introspect_node_in_network(
|
||||
context.document_network,
|
||||
context.network_interface.network(&[]).unwrap(),
|
||||
&imaginate_node,
|
||||
|network| {
|
||||
network
|
||||
@@ -2093,12 +2097,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
layout
|
||||
}
|
||||
|
||||
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
|
||||
string_properties(format!("Node '{}' cannot be found in library", document_node.name))
|
||||
fn unknown_node_properties(reference: &String) -> Vec<LayoutGroup> {
|
||||
string_properties(format!("Node '{}' cannot be found in library", reference))
|
||||
}
|
||||
|
||||
pub fn node_no_properties(document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
string_properties(if document_node.is_layer { "Layer has no properties" } else { "Node has no properties" })
|
||||
pub fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
string_properties(if context.network_interface.is_layer(&node_id, context.selection_network_path) {
|
||||
"Layer has no properties"
|
||||
} else {
|
||||
"Node has no properties"
|
||||
})
|
||||
}
|
||||
|
||||
pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
@@ -2108,13 +2116,18 @@ pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
}
|
||||
|
||||
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup {
|
||||
let name = document_node.name.clone();
|
||||
let layout = match super::document_node_types::resolve_document_node_type(&name) {
|
||||
Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context),
|
||||
None => unknown_node_properties(document_node),
|
||||
let reference = context.network_interface.reference(&node_id, context.selection_network_path).clone();
|
||||
let layout = if let Some(ref reference) = reference {
|
||||
match super::document_node_types::resolve_document_node_type(reference) {
|
||||
Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context),
|
||||
None => unknown_node_properties(reference),
|
||||
}
|
||||
} else {
|
||||
node_no_properties(document_node, node_id, context)
|
||||
};
|
||||
|
||||
LayoutGroup::Section {
|
||||
name,
|
||||
name: reference.unwrap_or_default(),
|
||||
visible: document_node.visible,
|
||||
id: node_id.0,
|
||||
layout,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::Type;
|
||||
use graphene_std::renderer::ClickTarget;
|
||||
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum FrontendGraphDataType {
|
||||
@@ -43,7 +44,8 @@ pub struct FrontendGraphInput {
|
||||
pub name: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub connected: Option<NodeId>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Option<OutputConnector>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -53,9 +55,8 @@ pub struct FrontendGraphOutput {
|
||||
pub name: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub connected: Vec<NodeId>,
|
||||
#[serde(rename = "connectedIndex")]
|
||||
pub connected_index: Vec<usize>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Vec<InputConnector>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -65,8 +66,9 @@ pub struct FrontendNode {
|
||||
pub is_layer: bool,
|
||||
#[serde(rename = "canBeLayer")]
|
||||
pub can_be_layer: bool,
|
||||
pub alias: String,
|
||||
pub name: String,
|
||||
pub reference: Option<String>,
|
||||
#[serde(rename = "displayName")]
|
||||
pub display_name: String,
|
||||
#[serde(rename = "primaryInput")]
|
||||
pub primary_input: Option<FrontendGraphInput>,
|
||||
#[serde(rename = "exposedInputs")]
|
||||
@@ -87,13 +89,9 @@ pub struct FrontendNode {
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeWire {
|
||||
#[serde(rename = "wireStart")]
|
||||
pub wire_start: NodeId,
|
||||
#[serde(rename = "wireStartOutputIndex")]
|
||||
pub wire_start_output_index: usize,
|
||||
pub wire_start: OutputConnector,
|
||||
#[serde(rename = "wireEnd")]
|
||||
pub wire_end: NodeId,
|
||||
#[serde(rename = "wireEndInputIndex")]
|
||||
pub wire_end_input_index: usize,
|
||||
pub wire_end: InputConnector,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
@@ -168,16 +166,16 @@ pub struct ContextMenuInformation {
|
||||
pub context_menu_data: ContextMenuData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeMetadata {
|
||||
/// Cache for all node click targets in node graph space. Ensure `update_click_target` is called when modifying a node property that changes its size. Currently this is `alias`, `inputs`, `is_layer`, and `metadata`.
|
||||
pub node_click_target: ClickTarget,
|
||||
/// Cache for all node inputs. Should be automatically updated when `update_click_target` is called.
|
||||
pub input_click_targets: Vec<ClickTarget>,
|
||||
/// Cache for all node outputs. Should be automatically updated when `update_click_target` is called.
|
||||
pub output_click_targets: Vec<ClickTarget>,
|
||||
/// Cache for all visibility buttons. Should be automatically updated when `update_click_target` is called.
|
||||
pub visibility_click_target: Option<ClickTarget>,
|
||||
/// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the end of the node.
|
||||
pub layer_width: Option<u32>,
|
||||
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendClickTargets {
|
||||
#[serde(rename = "nodeClickTargets")]
|
||||
pub node_click_targets: Vec<String>,
|
||||
#[serde(rename = "layerClickTargets")]
|
||||
pub layer_click_targets: Vec<String>,
|
||||
#[serde(rename = "portClickTargets")]
|
||||
pub port_click_targets: Vec<String>,
|
||||
#[serde(rename = "visibilityClickTargets")]
|
||||
pub visibility_click_targets: Vec<String>,
|
||||
#[serde(rename = "allNodesBoundingBox")]
|
||||
pub all_nodes_bounding_box: String,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
|
||||
for primary in 0..2 {
|
||||
@@ -57,7 +58,8 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
|
||||
let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
@@ -92,7 +94,8 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let grid_color = document.snapping_state.grid.grid_color;
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
@@ -142,7 +145,8 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
|
||||
let grid_color = document.snapping_state.grid.grid_color;
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
|
||||
@@ -24,10 +24,11 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
||||
}
|
||||
|
||||
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
@@ -62,10 +63,11 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape
|
||||
}
|
||||
|
||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
|
||||
+6
-9
@@ -10,12 +10,10 @@ pub struct PropertiesPanelMessageHandler {}
|
||||
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for PropertiesPanelMessageHandler {
|
||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
||||
let PropertiesPanelMessageHandlerData {
|
||||
node_graph_message_handler,
|
||||
executor,
|
||||
document_network: network,
|
||||
document_metadata: metadata,
|
||||
selected_nodes,
|
||||
network_interface,
|
||||
selection_path,
|
||||
document_name,
|
||||
executor,
|
||||
} = data;
|
||||
|
||||
match message {
|
||||
@@ -33,13 +31,12 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
let mut context = NodePropertiesContext {
|
||||
persistent_data,
|
||||
responses,
|
||||
nested_path: &node_graph_message_handler.network,
|
||||
executor,
|
||||
document_network: network,
|
||||
metadata,
|
||||
network_interface,
|
||||
selection_network_path: selection_path,
|
||||
};
|
||||
|
||||
let properties_sections = node_graph_message_handler.collate_properties(&mut context, selected_nodes);
|
||||
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut context);
|
||||
|
||||
let options_bar = vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::prelude::NodeGraphMessageHandler;
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use graph_craft::document::NodeNetwork;
|
||||
|
||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||
pub network_interface: &'a NodeNetworkInterface,
|
||||
pub selection_path: &'a [NodeId],
|
||||
pub document_name: &'a str,
|
||||
pub document_network: &'a NodeNetwork,
|
||||
pub document_metadata: &'a mut DocumentMetadata,
|
||||
pub selected_nodes: &'a SelectedNodes,
|
||||
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::NodeId;
|
||||
use super::network_interface::NodeTemplate;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
|
||||
@@ -17,10 +16,9 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CopyBufferEntry {
|
||||
pub nodes: HashMap<NodeId, DocumentNode>,
|
||||
pub nodes: Vec<(NodeId, NodeTemplate)>,
|
||||
pub selected: bool,
|
||||
pub visible: bool,
|
||||
pub locked: bool,
|
||||
pub collapsed: bool,
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::nodes::SelectedNodes;
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::FlowType;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::renderer::ClickTarget;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::transform::Footprint;
|
||||
@@ -11,7 +10,7 @@ use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorData;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
// ================
|
||||
@@ -22,14 +21,11 @@ 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 {
|
||||
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
artboards: HashSet<LayerNodeIdentifier>,
|
||||
folders: HashSet<LayerNodeIdentifier>,
|
||||
hidden: HashSet<NodeId>,
|
||||
locked: HashSet<NodeId>,
|
||||
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
vector_modify: HashMap<NodeId, VectorData>,
|
||||
pub upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub vector_modify: HashMap<NodeId, VectorData>,
|
||||
// TODO: Remove and derive from document_ptz in document message handler
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
@@ -39,10 +35,6 @@ impl Default for DocumentMetadata {
|
||||
Self {
|
||||
upstream_transforms: HashMap::new(),
|
||||
structure: HashMap::new(),
|
||||
artboards: HashSet::new(),
|
||||
folders: HashSet::new(),
|
||||
hidden: HashSet::new(),
|
||||
locked: HashSet::new(),
|
||||
vector_modify: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
@@ -67,9 +59,10 @@ impl DocumentMetadata {
|
||||
self.click_targets.get(&layer)
|
||||
}
|
||||
|
||||
/// Get vector data after the modification is appled
|
||||
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<VectorData> {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network);
|
||||
// TODO: Move into network interface so that it does not have to be passed as an argument
|
||||
/// Get vector data after the modification is applied
|
||||
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<VectorData> {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||
|
||||
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.vector_modify.get(&node)) {
|
||||
let mut modified = vector_data.clone();
|
||||
@@ -93,164 +86,7 @@ impl DocumentMetadata {
|
||||
fn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations {
|
||||
self.structure.entry(node_identifier).or_default()
|
||||
}
|
||||
|
||||
/// Layers excluding ones that are children of other layers in the list.
|
||||
pub fn shallowest_unique_layers(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<Vec<LayerNodeIdentifier>> {
|
||||
let mut sorted_layers = layers
|
||||
.map(|layer| {
|
||||
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
|
||||
layer_path.reverse();
|
||||
layer_path
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sorting here creates groups of similar UUID paths
|
||||
sorted_layers.sort();
|
||||
sorted_layers.dedup_by(|a, b| a.starts_with(b));
|
||||
sorted_layers
|
||||
}
|
||||
|
||||
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers
|
||||
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>, include_self: bool) -> Option<LayerNodeIdentifier> {
|
||||
layers
|
||||
.map(|layer| {
|
||||
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
|
||||
layer_path.reverse();
|
||||
|
||||
if !include_self || !self.is_artboard(layer) {
|
||||
layer_path.pop();
|
||||
}
|
||||
|
||||
layer_path
|
||||
})
|
||||
.reduce(|mut a, b| {
|
||||
a.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len())));
|
||||
a
|
||||
})
|
||||
.and_then(|layer| layer.last().copied())
|
||||
}
|
||||
|
||||
pub fn active_artboard(&self) -> LayerNodeIdentifier {
|
||||
self.artboards.iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT)
|
||||
}
|
||||
|
||||
pub fn all_artboards(&self) -> &HashSet<LayerNodeIdentifier> {
|
||||
&self.artboards
|
||||
}
|
||||
|
||||
pub fn is_folder(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.folders.contains(&layer)
|
||||
}
|
||||
|
||||
pub fn is_artboard(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.artboards.contains(&layer)
|
||||
}
|
||||
|
||||
pub fn node_is_visible(&self, layer: NodeId) -> bool {
|
||||
!self.hidden.contains(&layer)
|
||||
}
|
||||
|
||||
pub fn node_is_locked(&self, layer: NodeId) -> bool {
|
||||
self.locked.contains(&layer)
|
||||
}
|
||||
|
||||
/// Folders sorted from most nested to least nested
|
||||
pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<LayerNodeIdentifier> {
|
||||
let mut folders: Vec<_> = layers.filter(|layer| self.folders.contains(layer)).collect();
|
||||
folders.sort_by_cached_key(|a| std::cmp::Reverse(a.ancestors(self).count()));
|
||||
folders
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// DocumentMetadata: Selected layer modifications
|
||||
// ==============================================
|
||||
|
||||
impl DocumentMetadata {
|
||||
/// Loads the structure of layer nodes from a node graph.
|
||||
pub fn load_structure(&mut self, graph: &NodeNetwork) {
|
||||
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);
|
||||
self.artboards = HashSet::new();
|
||||
self.folders = HashSet::new();
|
||||
self.hidden = HashSet::new();
|
||||
self.locked = HashSet::new();
|
||||
|
||||
// Should refer to output node
|
||||
|
||||
let mut awaiting_horizontal_flow = vec![(NodeId(u64::MAX), LayerNodeIdentifier::ROOT_PARENT)];
|
||||
let mut awaiting_primary_flow = vec![];
|
||||
|
||||
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {
|
||||
let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow);
|
||||
// Skip the horizontal_root_node_id node
|
||||
for (current_node, current_node_id) in horizontal_flow_iter.skip(if horizontal_root_node_id == NodeId(u64::MAX) { 0 } else { 1 }) {
|
||||
if !current_node.visible {
|
||||
self.hidden.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.locked {
|
||||
self.locked.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.is_layer {
|
||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
|
||||
if !self.structure.contains_key(¤t_layer_node) {
|
||||
awaiting_primary_flow.push((current_node_id, parent_layer_node));
|
||||
|
||||
parent_layer_node.push_child(self, current_layer_node);
|
||||
parent_layer_node = current_layer_node;
|
||||
|
||||
if is_artboard(current_layer_node, graph) {
|
||||
self.artboards.insert(current_layer_node);
|
||||
}
|
||||
|
||||
if graph.nodes.get(¤t_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
|
||||
self.folders.insert(current_layer_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {
|
||||
let primary_flow_iter = graph.upstream_flow_back_from_nodes(vec![primary_root_node_id], FlowType::PrimaryFlow);
|
||||
// Skip the primary_root_node_id node
|
||||
for (current_node, current_node_id) in primary_flow_iter.skip(1) {
|
||||
if !current_node.visible {
|
||||
self.hidden.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.locked {
|
||||
self.locked.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.is_layer {
|
||||
// Create a new layer for the top of each stack, and add it as a child to the previous parent
|
||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
|
||||
if !self.structure.contains_key(¤t_layer_node) {
|
||||
parent_layer_node.push_child(self, current_layer_node);
|
||||
|
||||
// The layer nodes for the horizontal flow is itself
|
||||
awaiting_horizontal_flow.push((current_node_id, current_layer_node));
|
||||
|
||||
if is_artboard(current_layer_node, graph) {
|
||||
self.artboards.insert(current_layer_node);
|
||||
}
|
||||
|
||||
if graph.nodes.get(¤t_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
|
||||
self.folders.insert(current_layer_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
|
||||
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
|
||||
self.vector_modify.retain(|node, _| graph.nodes.contains_key(node));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// DocumentMetadata: Transforms
|
||||
// ============================
|
||||
@@ -351,23 +187,6 @@ impl DocumentMetadata {
|
||||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculates the document bounds in document space
|
||||
pub fn document_bounds_document_space(&self, include_artboards: bool) -> Option<[DVec2; 2]> {
|
||||
self.all_layers()
|
||||
.filter(|&layer| include_artboards || !self.is_artboard(layer))
|
||||
.filter_map(|layer| self.bounding_box_document(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculates the selected layer bounds in document space
|
||||
pub fn selected_bounds_document_space(&self, include_artboards: bool, selected_nodes: &SelectedNodes) -> Option<[DVec2; 2]> {
|
||||
selected_nodes
|
||||
.selected_layers(self)
|
||||
.filter(|&layer| include_artboards || !self.is_artboard(layer))
|
||||
.filter_map(|layer| self.bounding_box_document(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
|
||||
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||
@@ -398,7 +217,7 @@ impl Default for LayerNodeIdentifier {
|
||||
}
|
||||
|
||||
impl LayerNodeIdentifier {
|
||||
/// A conceptual node used to represent the UI-only "Export" node
|
||||
/// A conceptual layer used to represent the parent of layers that feed into the export
|
||||
pub const ROOT_PARENT: Self = LayerNodeIdentifier::new_unchecked(NodeId(0));
|
||||
|
||||
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
|
||||
@@ -407,13 +226,13 @@ impl LayerNodeIdentifier {
|
||||
Self(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })
|
||||
}
|
||||
|
||||
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node
|
||||
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node in the document network
|
||||
#[track_caller]
|
||||
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
|
||||
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {
|
||||
debug_assert!(
|
||||
network.nodes.get(&node_id).is_some_and(|node| node.is_layer),
|
||||
network_interface.is_layer(&node_id, &Vec::new()),
|
||||
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
|
||||
network.nodes.get(&node_id)
|
||||
network_interface.network(&[]).unwrap().nodes.get(&node_id)
|
||||
);
|
||||
Self::new_unchecked(node_id)
|
||||
}
|
||||
@@ -421,6 +240,7 @@ impl LayerNodeIdentifier {
|
||||
/// Access the node id of this layer
|
||||
pub fn to_node(self) -> NodeId {
|
||||
let id = NodeId(u64::from(self.0) - 1);
|
||||
|
||||
debug_assert!(id != NodeId(0), "LayerNodeIdentifier::ROOT_PARENT cannot be converted to NodeId");
|
||||
id
|
||||
}
|
||||
@@ -450,7 +270,7 @@ impl LayerNodeIdentifier {
|
||||
metadata.get_relations(self).and_then(|relations| relations.last_child)
|
||||
}
|
||||
|
||||
/// Does the layer have children?
|
||||
/// Does the layer have children? If so, then it is a folder
|
||||
pub fn has_children(self, metadata: &DocumentMetadata) -> bool {
|
||||
self.first_child(metadata).is_some()
|
||||
}
|
||||
@@ -672,7 +492,7 @@ impl<'a> DoubleEndedIterator for DescendantsIter<'a> {
|
||||
// =============
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct NodeRelations {
|
||||
pub struct NodeRelations {
|
||||
parent: Option<LayerNodeIdentifier>,
|
||||
previous_sibling: Option<LayerNodeIdentifier>,
|
||||
next_sibling: Option<LayerNodeIdentifier>,
|
||||
@@ -684,14 +504,6 @@ struct NodeRelations {
|
||||
// Helper functions
|
||||
// ================
|
||||
|
||||
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
return false;
|
||||
}
|
||||
let Some(node) = network.nodes.get(&layer.to_node()) else { return false };
|
||||
node.is_artboard()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree() {
|
||||
let mut metadata = DocumentMetadata::default();
|
||||
|
||||
@@ -404,7 +404,7 @@ impl fmt::Display for SnappingOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct PTZ {
|
||||
pub pan: DVec2,
|
||||
|
||||
@@ -2,5 +2,6 @@ pub mod clipboards;
|
||||
pub mod document_metadata;
|
||||
pub mod error;
|
||||
pub mod misc;
|
||||
pub mod network_interface;
|
||||
pub mod nodes;
|
||||
pub mod transformation;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
|
||||
@@ -33,7 +34,6 @@ impl serde::Serialize for JsRawBuffer {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
pub struct LayerPanelEntry {
|
||||
pub id: NodeId,
|
||||
pub name: String,
|
||||
pub alias: String,
|
||||
pub tooltip: String,
|
||||
#[serde(rename = "childrenAllowed")]
|
||||
@@ -50,68 +50,75 @@ pub struct LayerPanelEntry {
|
||||
pub parents_unlocked: bool,
|
||||
#[serde(rename = "parentId")]
|
||||
pub parent_id: Option<NodeId>,
|
||||
pub selected: bool,
|
||||
#[serde(rename = "inSelectedNetwork")]
|
||||
pub in_selected_network: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
pub struct SelectedNodes(pub Vec<NodeId>);
|
||||
|
||||
impl SelectedNodes {
|
||||
pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
|
||||
layer.ancestors(metadata).all(|layer| {
|
||||
pub fn layer_visible(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
|
||||
layer.ancestors(network_interface.document_metadata()).all(|layer| {
|
||||
if layer != LayerNodeIdentifier::ROOT_PARENT {
|
||||
metadata.node_is_visible(layer.to_node())
|
||||
network_interface.is_visible(&layer.to_node(), &[])
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn selected_visible_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(metadata).filter(move |&layer| self.layer_visible(layer, metadata))
|
||||
pub fn selected_visible_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(network_interface.document_metadata())
|
||||
.filter(move |&layer| self.layer_visible(layer, network_interface))
|
||||
}
|
||||
|
||||
pub fn layer_locked(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
|
||||
layer.ancestors(metadata).any(|layer| {
|
||||
pub fn layer_locked(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
|
||||
layer.ancestors(network_interface.document_metadata()).any(|layer| {
|
||||
if layer != LayerNodeIdentifier::ROOT_PARENT {
|
||||
metadata.node_is_locked(layer.to_node())
|
||||
network_interface.is_locked(&layer.to_node(), &[])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn selected_unlocked_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(metadata).filter(move |&layer| !self.layer_locked(layer, metadata))
|
||||
pub fn selected_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(network_interface.document_metadata())
|
||||
.filter(move |&layer| !self.layer_locked(layer, network_interface))
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlocked_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(metadata)
|
||||
.filter(move |&layer| self.layer_visible(layer, metadata) && !self.layer_locked(layer, metadata))
|
||||
pub fn selected_visible_and_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(network_interface.document_metadata())
|
||||
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
|
||||
}
|
||||
|
||||
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
|
||||
}
|
||||
|
||||
pub fn selected_layers_except_artboards<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(metadata).filter(move |&layer| !metadata.is_artboard(layer))
|
||||
pub fn selected_layers_except_artboards<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.selected_layers(network_interface.document_metadata())
|
||||
.filter(move |&layer| !network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
|
||||
self.selected_layers(metadata).any(|selected| selected == layer)
|
||||
}
|
||||
|
||||
// All selected nodes must be in the same network
|
||||
pub fn selected_nodes<'a>(&'a self, network: &'a NodeNetwork) -> impl Iterator<Item = &NodeId> + '_ {
|
||||
self.0
|
||||
.iter()
|
||||
.filter(|node_id| network.nodes.contains_key(*node_id) || **node_id == network.imports_metadata.0 || **node_id == network.exports_metadata.0)
|
||||
pub fn selected_nodes(&self) -> impl Iterator<Item = &NodeId> + '_ {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn network_has_selected_nodes(&self, network: &NodeNetwork) -> bool {
|
||||
self.0.iter().any(|node_id| network.nodes.contains_key(node_id))
|
||||
}
|
||||
|
||||
pub fn has_selected_nodes(&self) -> bool {
|
||||
!self.0.is_empty()
|
||||
}
|
||||
@@ -120,38 +127,11 @@ impl SelectedNodes {
|
||||
self.0.retain(f);
|
||||
}
|
||||
|
||||
// TODO: This function is run when a node in the layer panel is currently selected, and a new node is selected in the graph, as well as when a node is currently selected in the graph and a node in the layer panel is selected. These are fundamentally different operations, since different nodes should be selected in each case, but cannot be distinguished. Currently it is not possible to shift+click a node in the node graph while a layer is selected. Instead of set_selected_nodes, add_selected_nodes should be used.
|
||||
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &[NodeId]) {
|
||||
let Some(network) = document_network.nested_network(network_path) else { return };
|
||||
|
||||
let mut new_nodes = new;
|
||||
|
||||
// If any nodes to add are in the document network, clear selected nodes in the current network
|
||||
if new_nodes.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
|
||||
new_nodes.retain(|selected_node| {
|
||||
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
|
||||
});
|
||||
}
|
||||
// If not, then clear any nodes that are not in the current network
|
||||
else {
|
||||
new_nodes.retain(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
|
||||
}
|
||||
|
||||
self.0 = new_nodes;
|
||||
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) {
|
||||
self.0 = new;
|
||||
}
|
||||
|
||||
pub fn add_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &[NodeId]) {
|
||||
let Some(network) = document_network.nested_network(network_path) else { return };
|
||||
|
||||
// If the nodes to add are in the document network, clear selected nodes in the current network
|
||||
if new.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
|
||||
self.retain_selected_nodes(|selected_node| {
|
||||
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
|
||||
});
|
||||
} else {
|
||||
self.retain_selected_nodes(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
|
||||
}
|
||||
|
||||
pub fn add_selected_nodes(&mut self, new: Vec<NodeId>) {
|
||||
self.0.extend(new);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
@@ -6,7 +7,6 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
|
||||
use graph_craft::document::NodeNetwork;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
use graphene_core::vector::VectorModificationType;
|
||||
@@ -53,7 +53,9 @@ impl OriginalTransforms {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], document_network: &NodeNetwork, document_metadata: &DocumentMetadata, shape_editor: Option<&'a ShapeState>) {
|
||||
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
|
||||
let document_metadata = network_interface.document_metadata();
|
||||
|
||||
match self {
|
||||
OriginalTransforms::Layer(layer_map) => {
|
||||
layer_map.retain(|layer, _| selected.contains(layer));
|
||||
@@ -73,7 +75,7 @@ impl OriginalTransforms {
|
||||
if path_map.contains_key(&layer) {
|
||||
continue;
|
||||
}
|
||||
let Some(vector_data) = document_metadata.compute_modified_vector(layer, document_network) else {
|
||||
let Some(vector_data) = network_interface.document_metadata().compute_modified_vector(layer, network_interface) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||
@@ -343,8 +345,7 @@ impl TransformOperation {
|
||||
pub struct Selected<'a> {
|
||||
pub selected: &'a [LayerNodeIdentifier],
|
||||
pub responses: &'a mut VecDeque<Message>,
|
||||
pub document_network: &'a NodeNetwork,
|
||||
pub document_metadata: &'a DocumentMetadata,
|
||||
pub network_interface: &'a NodeNetworkInterface,
|
||||
pub original_transforms: &'a mut OriginalTransforms,
|
||||
pub pivot: &'a mut DVec2,
|
||||
pub shape_editor: Option<&'a ShapeState>,
|
||||
@@ -358,8 +359,7 @@ impl<'a> Selected<'a> {
|
||||
pivot: &'a mut DVec2,
|
||||
selected: &'a [LayerNodeIdentifier],
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document_network: &'a NodeNetwork,
|
||||
document_metadata: &'a DocumentMetadata,
|
||||
network_interface: &'a NodeNetworkInterface,
|
||||
shape_editor: Option<&'a ShapeState>,
|
||||
tool_type: &'a ToolType,
|
||||
) -> Self {
|
||||
@@ -368,13 +368,12 @@ impl<'a> Selected<'a> {
|
||||
*original_transforms = OriginalTransforms::Layer(HashMap::new());
|
||||
}
|
||||
|
||||
original_transforms.update(selected, document_network, document_metadata, shape_editor);
|
||||
original_transforms.update(selected, network_interface, shape_editor);
|
||||
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document_network,
|
||||
document_metadata,
|
||||
network_interface,
|
||||
original_transforms,
|
||||
pivot,
|
||||
shape_editor,
|
||||
@@ -386,7 +385,7 @@ impl<'a> Selected<'a> {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document_network, self.document_metadata))
|
||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -397,7 +396,7 @@ impl<'a> Selected<'a> {
|
||||
let [min, max] = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|&layer| self.document_metadata.bounding_box_viewport(layer))
|
||||
.filter_map(|&layer| self.network_interface.document_metadata().bounding_box_viewport(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
.unwrap_or_default();
|
||||
(min + max) / 2.
|
||||
@@ -446,14 +445,14 @@ impl<'a> Selected<'a> {
|
||||
pub fn apply_transformation(&mut self, transformation: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for layer_ancestors in self.document_metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
||||
let layer = *layer_ancestors.last().unwrap();
|
||||
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]) {
|
||||
match &mut self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document_metadata, layer, layer_transforms.get(&layer), transformation, self.responses),
|
||||
OriginalTransforms::Layer(layer_transforms) => {
|
||||
Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses)
|
||||
}
|
||||
OriginalTransforms::Path(path_transforms) => {
|
||||
if let Some(initial_points) = path_transforms.get_mut(&layer) {
|
||||
Self::transform_path(self.document_metadata, layer, initial_points, transformation, self.responses)
|
||||
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user