mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00: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:
parent
ea44d1440a
commit
0dbbabe73e
77 changed files with 11361 additions and 8011 deletions
|
@ -13,6 +13,7 @@ use editor::consts::FILE_SAVE_SUFFIX;
|
|||
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
|
||||
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use editor::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use editor::messages::portfolio::utility_types::Platform;
|
||||
use editor::messages::prelude::*;
|
||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
||||
|
@ -525,7 +526,7 @@ impl EditorHandle {
|
|||
|
||||
/// Move a layer to within a folder and placed down at the given index.
|
||||
/// If the folder is `None`, it is inserted into the document root.
|
||||
/// If the insert index is `None`, it is inserted at the end of the folder (equivalent to index infinity).
|
||||
/// If the insert index is `None`, it is inserted at the start of the folder.
|
||||
#[wasm_bindgen(js_name = moveLayerInTree)]
|
||||
pub fn move_layer_in_tree(&self, insert_parent_id: Option<u64>, insert_index: Option<usize>) {
|
||||
let insert_parent_id = insert_parent_id.map(NodeId);
|
||||
|
@ -533,7 +534,7 @@ impl EditorHandle {
|
|||
|
||||
let message = DocumentMessage::MoveSelectedLayersTo {
|
||||
parent,
|
||||
insert_index: insert_index.map(|x| x as isize).unwrap_or(-1),
|
||||
insert_index: insert_index.unwrap_or_default(),
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -542,7 +543,10 @@ impl EditorHandle {
|
|||
#[wasm_bindgen(js_name = setLayerName)]
|
||||
pub fn set_layer_name(&self, id: u64, name: String) {
|
||||
let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
|
||||
let message = GraphOperationMessage::SetName { layer, name };
|
||||
let message = NodeGraphMessage::SetDisplayName {
|
||||
node_id: layer.to_node(),
|
||||
alias: name,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -564,7 +568,12 @@ impl EditorHandle {
|
|||
#[wasm_bindgen(js_name = createNode)]
|
||||
pub fn create_node(&self, node_type: String, x: i32, y: i32) {
|
||||
let id = NodeId(generate_uuid());
|
||||
let message = NodeGraphMessage::CreateNode { node_id: Some(id), node_type, x, y };
|
||||
let message = NodeGraphMessage::CreateNodeFromContextMenu {
|
||||
node_id: Some(id),
|
||||
node_type,
|
||||
x: x / 24,
|
||||
y: y / 24,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -575,13 +584,6 @@ impl EditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Go back a certain number of nested levels
|
||||
#[wasm_bindgen(js_name = exitNestedNetwork)]
|
||||
pub fn exit_nested_network(&self, steps_back: usize) {
|
||||
let message = NodeGraphMessage::ExitNestedNetwork { steps_back };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Pastes an image
|
||||
#[wasm_bindgen(js_name = pasteImage)]
|
||||
pub fn paste_image(&self, image_data: Vec<u8>, width: u32, height: u32, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
||||
|
@ -602,7 +604,7 @@ impl EditorHandle {
|
|||
#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
|
||||
pub fn toggle_node_visibility_layer(&self, id: u64) {
|
||||
let node_id = NodeId(id);
|
||||
let message = GraphOperationMessage::ToggleVisibility { node_id };
|
||||
let message = NodeGraphMessage::ToggleVisibility { node_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -619,9 +621,8 @@ impl EditorHandle {
|
|||
|
||||
/// Toggle lock state of a layer from the layer list
|
||||
#[wasm_bindgen(js_name = toggleLayerLock)]
|
||||
pub fn toggle_layer_lock(&self, id: u64) {
|
||||
let node_id = NodeId(id);
|
||||
let message = GraphOperationMessage::ToggleLocked { node_id };
|
||||
pub fn toggle_layer_lock(&self, node_id: u64) {
|
||||
let message = NodeGraphMessage::ToggleLocked { node_id: NodeId(node_id) };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -633,10 +634,19 @@ impl EditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Set the active panel to the most recently clicked panel
|
||||
#[wasm_bindgen(js_name = setActivePanel)]
|
||||
pub fn set_active_panel(&self, panel: String) {
|
||||
let message = PortfolioMessage::SetActivePanel { panel: panel.into() };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Toggle display type for a layer
|
||||
#[wasm_bindgen(js_name = setToNodeOrLayer)]
|
||||
pub fn set_to_node_or_layer(&self, id: u64, is_layer: bool) {
|
||||
let node_id = NodeId(id);
|
||||
let message = DocumentMessage::StartTransaction;
|
||||
self.dispatch(message);
|
||||
let message = NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -680,16 +690,44 @@ impl EditorHandle {
|
|||
});
|
||||
|
||||
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||
for node in document.network.nodes.values_mut().filter(|d| d.name == "Artboard") {
|
||||
if let Some(network) = node.implementation.get_network_mut() {
|
||||
for node in network.nodes.values_mut() {
|
||||
if node.name == "To Artboard" {
|
||||
node.implementation = DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>");
|
||||
if node.inputs.len() != 6 {
|
||||
node.inputs.insert(2, NodeInput::value(TaggedValue::IVec2(glam::IVec2::default()), false));
|
||||
}
|
||||
for node in document
|
||||
.network_interface
|
||||
.network_metadata(&[])
|
||||
.unwrap()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Artboard"))
|
||||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let Some(document_node) = document.network_interface.network(&[]).unwrap().nodes.get(&node) else {
|
||||
log::error!("Could not get document node in document network");
|
||||
return;
|
||||
};
|
||||
if let Some(network) = document_node.implementation.get_network() {
|
||||
let mut nodes_to_upgrade = Vec::new();
|
||||
for (node_id, _) in network.nodes.iter().collect::<Vec<_>>() {
|
||||
if document.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == "To Artboard")
|
||||
&& document
|
||||
.network_interface
|
||||
.network(&[])
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get(node_id)
|
||||
.is_some_and(|document_node| document_node.inputs.len() != 6)
|
||||
{
|
||||
nodes_to_upgrade.push(*node_id);
|
||||
}
|
||||
}
|
||||
for node_id in nodes_to_upgrade {
|
||||
document
|
||||
.network_interface
|
||||
.set_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
|
||||
document
|
||||
.network_interface
|
||||
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,60 +760,87 @@ impl EditorHandle {
|
|||
|
||||
let mut updated_nodes = HashSet::new();
|
||||
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||
document.metadata.load_structure(&document.network);
|
||||
for node in document.network.nodes.iter().filter(|(_, d)| d.name == "Merge").map(|(id, _)| *id).collect::<Vec<_>>() {
|
||||
let layer = LayerNodeIdentifier::new(node, &document.network);
|
||||
if document.metadata.is_folder(layer) {
|
||||
document.network_interface.load_structure();
|
||||
for node in document
|
||||
.network_interface
|
||||
.network_metadata(&[])
|
||||
.unwrap()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Merge"))
|
||||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let layer = LayerNodeIdentifier::new(node, &document.network_interface);
|
||||
if layer.has_children(document.metadata()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let bounds = LayerBounds::new(&document.metadata, layer);
|
||||
let bounds = LayerBounds::new(document.metadata(), layer);
|
||||
|
||||
let mut responses = VecDeque::new();
|
||||
let mut shape = None;
|
||||
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), &mut document.network, &mut document.metadata, &mut document.node_graph_handler, &mut responses) {
|
||||
modify_inputs.modify_existing_inputs("Transform", |inputs, node_id, metadata| {
|
||||
if !updated_nodes.insert(node_id) {
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) {
|
||||
let Some(transform_node_id) = modify_inputs.get_existing_node_id("Transform") else { return };
|
||||
if !updated_nodes.insert(transform_node_id) {
|
||||
return;
|
||||
}
|
||||
let Some(inputs) = modify_inputs.network_interface.network(&[]).unwrap().nodes.get(&transform_node_id).map(|node| &node.inputs) else {
|
||||
log::error!("Could not get transform node in document network");
|
||||
return;
|
||||
};
|
||||
let transform = get_current_transform(inputs);
|
||||
let upstream_transform = modify_inputs.network_interface.document_metadata().upstream_transform(transform_node_id);
|
||||
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(inputs))));
|
||||
|
||||
let transform = get_current_transform(inputs);
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(inputs))));
|
||||
|
||||
update_transform(inputs, pivot_transform * transform * pivot_transform.inverse());
|
||||
});
|
||||
modify_inputs.modify_existing_inputs("Shape", |inputs, node_id, _metadata| {
|
||||
if !updated_nodes.insert(node_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let path_data = match &inputs[0].as_value() {
|
||||
Some(TaggedValue::Subpaths(translation)) => translation,
|
||||
_ => &Vec::new(),
|
||||
};
|
||||
|
||||
let colinear_manipulators = match &inputs[1].as_value() {
|
||||
Some(TaggedValue::PointIds(translation)) => translation,
|
||||
_ => &Vec::new(),
|
||||
};
|
||||
|
||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
.collect();
|
||||
|
||||
shape = Some((node_id, VectorModification::create_from_vector(&vector_data)));
|
||||
});
|
||||
update_transform(&mut document.network_interface, &transform_node_id, pivot_transform * transform * pivot_transform.inverse());
|
||||
}
|
||||
if let Some((id, modification)) = shape {
|
||||
let metadata = document.network.nodes.remove(&id).map(|node| node.metadata).unwrap_or_default();
|
||||
let node_type = resolve_document_node_type("Path").unwrap();
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) {
|
||||
let Some(shape_node_id) = modify_inputs.get_existing_node_id("Shape") else { return };
|
||||
if !updated_nodes.insert(shape_node_id) {
|
||||
return;
|
||||
}
|
||||
let Some(shape_node) = modify_inputs.network_interface.network(&[]).unwrap().nodes.get(&shape_node_id) else {
|
||||
log::error!("Could not get shape node in document network");
|
||||
return;
|
||||
};
|
||||
let path_data = match &shape_node.inputs[0].as_value() {
|
||||
Some(TaggedValue::Subpaths(translation)) => translation,
|
||||
_ => &Vec::new(),
|
||||
};
|
||||
|
||||
let document_node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))], metadata);
|
||||
document.network.nodes.insert(id, document_node);
|
||||
let colinear_manipulators = match &shape_node.inputs[1].as_value() {
|
||||
Some(TaggedValue::PointIds(translation)) => translation,
|
||||
_ => &Vec::new(),
|
||||
};
|
||||
|
||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
.collect();
|
||||
|
||||
shape = Some((shape_node_id, VectorModification::create_from_vector(&vector_data)));
|
||||
}
|
||||
|
||||
if let Some((node_id, modification)) = shape {
|
||||
let node_type = resolve_document_node_type("Path").unwrap();
|
||||
let document_node = node_type
|
||||
.node_template_input_override([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))])
|
||||
.document_node;
|
||||
|
||||
let node_metadata = document.network_interface.node_metadata(&node_id, &[]).cloned().unwrap_or_default();
|
||||
|
||||
document.network_interface.insert_node(
|
||||
node_id,
|
||||
NodeTemplate {
|
||||
document_node,
|
||||
persistent_node_metadata: node_metadata.persistent_metadata,
|
||||
},
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue