Pasting and duplicating nodes (#902)
* Pasting and duplicating nodes * Rebind duplication * Update selection on duplicate
This commit is contained in:
parent
8f4f7b3cf1
commit
af001f8db6
|
|
@ -227,6 +227,9 @@ pub enum FrontendMessage {
|
|||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
},
|
||||
UpdateNodeGraphSelection {
|
||||
selected: Vec<NodeId>,
|
||||
},
|
||||
UpdateNodeGraphVisibility {
|
||||
visible: bool,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ pub fn default_mapping() -> Mapping {
|
|||
// NodeGraphMessage
|
||||
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||
entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ pub enum NodeGraphMessage {
|
|||
input_node: u64,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
Copy,
|
||||
CreateNode {
|
||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
||||
node_id: Option<NodeId>,
|
||||
|
|
@ -21,6 +22,7 @@ pub enum NodeGraphMessage {
|
|||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
Cut,
|
||||
DeleteNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
|
|
@ -32,6 +34,7 @@ pub enum NodeGraphMessage {
|
|||
DoubleClickNode {
|
||||
node: NodeId,
|
||||
},
|
||||
DuplicateSelectedNodes,
|
||||
ExitNestedNetwork {
|
||||
depth_of_nesting: usize,
|
||||
},
|
||||
|
|
@ -47,6 +50,9 @@ pub enum NodeGraphMessage {
|
|||
OpenNodeGraph {
|
||||
layer_path: Vec<document_legacy::LayerId>,
|
||||
},
|
||||
PasteNodes {
|
||||
serialized_nodes: String,
|
||||
},
|
||||
SelectNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -243,6 +243,16 @@ impl NodeGraphMessageHandler {
|
|||
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
|
||||
}
|
||||
|
||||
/// Updates the frontend's selection state inline with the backend
|
||||
fn update_selected(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateNodeGraphSelection {
|
||||
selected: self.selected_nodes.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_references_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if network.inputs.iter().any(|&id| id == node_id) {
|
||||
warn!("Deleting input node");
|
||||
|
|
@ -329,6 +339,26 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
Self::send_graph(network, responses);
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
NodeGraphMessage::Copy => {
|
||||
let Some(network) = self.get_active_network_mut(document) else {
|
||||
error!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
// Collect the selected nodes
|
||||
let selected_nodes = self
|
||||
.selected_nodes
|
||||
.iter()
|
||||
.filter(|&&id| !network.inputs.contains(&id) && network.output != id) // Don't copy input or output nodes
|
||||
.filter_map(|id| network.nodes.get(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Prefix to show that this is nodes
|
||||
let mut copy_text = String::from("graphite/nodes: ");
|
||||
copy_text += &serde_json::to_string(&selected_nodes).expect("Could not serialize paste");
|
||||
|
||||
responses.push_back(FrontendMessage::TriggerTextCopy { copy_text }.into());
|
||||
}
|
||||
NodeGraphMessage::CreateNode { node_id, node_type, x, y } => {
|
||||
let node_id = node_id.unwrap_or_else(crate::application::generate_uuid);
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
|
|
@ -372,6 +402,10 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
);
|
||||
Self::send_graph(network, responses);
|
||||
}
|
||||
NodeGraphMessage::Cut => {
|
||||
responses.push_back(NodeGraphMessage::Copy.into());
|
||||
responses.push_back(NodeGraphMessage::DeleteSelectedNodes.into());
|
||||
}
|
||||
NodeGraphMessage::DeleteNode { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
if self.remove_node(network, node_id) {
|
||||
|
|
@ -419,6 +453,31 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
Self::send_graph(network, responses);
|
||||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
self.update_selected(responses);
|
||||
}
|
||||
NodeGraphMessage::DuplicateSelectedNodes => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
let mut new_selected = Vec::new();
|
||||
for &id in &self.selected_nodes {
|
||||
// Don't allow copying input or output nodes.
|
||||
if id != network.output && !network.inputs.contains(&id) {
|
||||
if let Some(node) = network.nodes.get(&id) {
|
||||
let new_id = crate::application::generate_uuid();
|
||||
let mut node = node.clone();
|
||||
|
||||
// Shift duplicated nodes
|
||||
node.metadata.position.0 += 2;
|
||||
node.metadata.position.1 += 2;
|
||||
|
||||
network.nodes.insert(new_id, node);
|
||||
new_selected.push(new_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.selected_nodes = new_selected;
|
||||
Self::send_graph(network, responses);
|
||||
self.update_selected(responses);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
|
||||
self.selected_nodes = Vec::new();
|
||||
|
|
@ -484,6 +543,32 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
}
|
||||
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
let data = match serde_json::from_str::<Vec<DocumentNode>>(&serialized_nodes) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
warn!("Invalid node data {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.selected_nodes.clear();
|
||||
for node in data {
|
||||
let id = crate::application::generate_uuid();
|
||||
network.nodes.insert(id, node);
|
||||
|
||||
// Select the newly pasted node
|
||||
self.selected_nodes.push(id);
|
||||
}
|
||||
|
||||
Self::send_graph(network, responses);
|
||||
self.update_selected(responses);
|
||||
}
|
||||
NodeGraphMessage::SelectNodes { nodes } => {
|
||||
self.selected_nodes = nodes;
|
||||
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
|
||||
|
|
@ -582,7 +667,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||
|
||||
fn actions(&self) -> ActionList {
|
||||
if self.layer_path.is_some() && !self.selected_nodes.is_empty() {
|
||||
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes,)
|
||||
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes)
|
||||
} else {
|
||||
actions!(NodeGraphMessageDiscriminant;)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ import { defineComponent, nextTick } from "vue";
|
|||
|
||||
import type { IconName } from "@/utility-functions/icons";
|
||||
|
||||
import type { FrontendNodeLink } from "@/wasm-communication/messages";
|
||||
import { UpdateNodeGraphSelection, FrontendNodeLink } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
|
|
@ -721,6 +721,10 @@ export default defineComponent({
|
|||
const outputPort2 = document.querySelectorAll(`[data-port="output"]`)[6] as HTMLDivElement | undefined;
|
||||
const inputPort2 = document.querySelectorAll(`[data-port="input"]`)[3] as HTMLDivElement | undefined;
|
||||
if (outputPort2 && inputPort2) this.createWirePath(outputPort2, inputPort2.getBoundingClientRect(), true, false);
|
||||
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (updateNodeGraphSelection) => {
|
||||
this.selected = updateNodeGraphSelection.selected;
|
||||
});
|
||||
},
|
||||
components: {
|
||||
IconLabel,
|
||||
|
|
|
|||
|
|
@ -262,6 +262,8 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
item.getAsString((text) => {
|
||||
if (text.startsWith("graphite/layer: ")) {
|
||||
editor.instance.pasteSerializedData(text.substring(16, text.length));
|
||||
} else if (text.startsWith("graphite/nodes: ")) {
|
||||
editor.instance.pasteSerializedNodes(text.substring(16, text.length));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,17 @@ export class UpdateNodeGraph extends JsMessage {
|
|||
@Type(() => FrontendNodeLink)
|
||||
readonly links!: FrontendNodeLink[];
|
||||
}
|
||||
|
||||
export class UpdateNodeTypes extends JsMessage {
|
||||
@Type(() => FrontendNode)
|
||||
readonly nodeTypes!: FrontendNodeType[];
|
||||
}
|
||||
|
||||
export class UpdateNodeGraphSelection extends JsMessage {
|
||||
@Type(() => BigInt)
|
||||
readonly selected!: bigint[];
|
||||
}
|
||||
|
||||
export class UpdateNodeGraphVisibility extends JsMessage {
|
||||
readonly visible!: boolean;
|
||||
}
|
||||
|
|
@ -1407,6 +1413,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateMouseCursor,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeGraphBarLayout,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeTypes,
|
||||
UpdateNodeGraphVisibility,
|
||||
UpdateOpenDocumentsList,
|
||||
|
|
|
|||
|
|
@ -627,6 +627,13 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Pastes the nodes based on serialized data
|
||||
#[wasm_bindgen(js_name = pasteSerializedNodes)]
|
||||
pub fn paste_serialized_nodes(&self, serialized_nodes: String) {
|
||||
let message = NodeGraphMessage::PasteNodes { serialized_nodes };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Notifies the backend that the user double clicked a node
|
||||
#[wasm_bindgen(js_name = doubleClickNode)]
|
||||
pub fn double_click_node(&self, node: u64) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue