Improve history states (#932)

* Add some more history states

* Fix undo whilst drawing

* Paste image history

* Toggle output and preview history

* Code review nits

* Remove extra '{'

* Fix typo

* Fix about.toml

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-01-01 22:02:44 +00:00 committed by Keavon Chambers
parent 6e142627a3
commit 2bcc3d3baf
16 changed files with 282 additions and 190 deletions

View File

@ -17,9 +17,10 @@ ignore-dev-dependencies = true
workarounds = ["ring"] workarounds = ["ring"]
# https://raw.githubusercontent.com/briansmith/webpki/main/LICENSE looks like MIT to me # https://raw.githubusercontent.com/briansmith/webpki/main/LICENSE
# is the ISC license but test code within the repo is BSD-3-Clause, but is not compiled into the crate when we use it
[webpki.clarify] [webpki.clarify]
license = "MIT" license = "ISC"
[[webpki.clarify.files]] [[webpki.clarify.files]]
path = 'LICENSE' path = 'LICENSE'

View File

@ -187,6 +187,7 @@ pub enum DocumentMessage {
toggle_angle: bool, toggle_angle: bool,
}, },
Undo, Undo,
UndoFinished,
UngroupLayers { UngroupLayers {
folder_path: Vec<LayerId>, folder_path: Vec<LayerId>,
}, },

View File

@ -53,6 +53,9 @@ pub struct DocumentMessageHandler {
pub document_undo_history: VecDeque<DocumentSave>, pub document_undo_history: VecDeque<DocumentSave>,
#[serde(skip)] #[serde(skip)]
pub document_redo_history: VecDeque<DocumentSave>, pub document_redo_history: VecDeque<DocumentSave>,
/// Don't allow aborting transactions whilst undoing to avoid #559
#[serde(skip)]
undo_in_progress: bool,
#[serde(with = "vectorize_layer_metadata")] #[serde(with = "vectorize_layer_metadata")]
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>, pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
@ -85,6 +88,7 @@ impl Default for DocumentMessageHandler {
document_undo_history: VecDeque::new(), document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(), document_redo_history: VecDeque::new(),
undo_in_progress: false,
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(), layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
layer_range_selection_reference: Vec::new(), layer_range_selection_reference: Vec::new(),
@ -190,9 +194,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
// Messages // Messages
AbortTransaction => { AbortTransaction => {
if !self.undo_in_progress {
self.undo(responses).unwrap_or_else(|e| warn!("{}", e)); self.undo(responses).unwrap_or_else(|e| warn!("{}", e));
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
} }
}
AddSelectedLayers { additional_layers } => { AddSelectedLayers { additional_layers } => {
for layer_path in &additional_layers { for layer_path in &additional_layers {
responses.extend(self.select_layer(layer_path, &persistent_data.font_cache)); responses.extend(self.select_layer(layer_path, &persistent_data.font_cache));
@ -509,7 +515,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
// Set a random seed input // Set a random seed input
responses.push_back( responses.push_back(
NodeGraphMessage::SetInputValue { NodeGraphMessage::SetInputValue {
node: *imaginate_node.last().unwrap(), node_id: *imaginate_node.last().unwrap(),
input_index: 1, input_index: 1,
value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64), value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64),
} }
@ -541,6 +547,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(BroadcastEvent::DocumentIsDirty.into());
} }
PasteImage { mime, image_data, mouse } => { PasteImage { mime, image_data, mouse } => {
responses.push_back(DocumentMessage::StartTransaction.into());
let path = vec![generate_uuid()]; let path = vec![generate_uuid()];
responses.push_back( responses.push_back(
DocumentOperation::AddImage { DocumentOperation::AddImage {
@ -833,12 +841,15 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
); );
} }
Undo => { Undo => {
self.undo_in_progress = true;
responses.push_back(BroadcastEvent::ToolAbort.into()); responses.push_back(BroadcastEvent::ToolAbort.into());
responses.push_back(DocumentHistoryBackward.into()); responses.push_back(DocumentHistoryBackward.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.push_back(RenderDocument.into()); responses.push_back(RenderDocument.into());
responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); responses.push_back(FolderChanged { affected_folder_path: vec![] }.into());
responses.push_back(UndoFinished.into());
} }
UndoFinished => self.undo_in_progress = false,
UngroupLayers { folder_path } => { UngroupLayers { folder_path } => {
// Select all the children of the folder // Select all the children of the folder
let select = self.document_legacy.folder_children_paths(&folder_path); let select = self.document_legacy.folder_children_paths(&folder_path);
@ -1353,6 +1364,8 @@ impl DocumentMessageHandler {
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()) responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
} }
responses.push_back(NodeGraphMessage::SendGraph.into());
Ok(()) Ok(())
} }
None => Err(EditorError::NoTransactionInProgress), None => Err(EditorError::NoTransactionInProgress),
@ -1391,6 +1404,8 @@ impl DocumentMessageHandler {
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()) responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
} }
responses.push_back(NodeGraphMessage::SendGraph.into());
Ok(()) Ok(())
} }
None => Err(EditorError::NoTransactionInProgress), None => Err(EditorError::NoTransactionInProgress),

View File

@ -1,7 +1,8 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::LayerId; use document_legacy::LayerId;
use graph_craft::document::{value::TaggedValue, NodeId}; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, DocumentMessage, NodeGraph)] #[impl_message(Message, DocumentMessage, NodeGraph)]
@ -43,6 +44,10 @@ pub enum NodeGraphMessage {
input_index: usize, input_index: usize,
new_exposed: bool, new_exposed: bool,
}, },
InsertNode {
node_id: NodeId,
document_node: DocumentNode,
},
MoveSelectedNodes { MoveSelectedNodes {
displacement_x: i32, displacement_x: i32,
displacement_y: i32, displacement_y: i32,
@ -56,14 +61,20 @@ pub enum NodeGraphMessage {
SelectNodes { SelectNodes {
nodes: Vec<NodeId>, nodes: Vec<NodeId>,
}, },
SendGraph,
SetDrawing { SetDrawing {
new_drawing: bool, new_drawing: bool,
}, },
SetInputValue { SetInputValue {
node: NodeId, node_id: NodeId,
input_index: usize, input_index: usize,
value: TaggedValue, value: TaggedValue,
}, },
SetNodeInput {
node_id: NodeId,
input_index: usize,
input: NodeInput,
},
SetQualifiedInputValue { SetQualifiedInputValue {
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
@ -74,7 +85,11 @@ pub enum NodeGraphMessage {
node_id: NodeId, node_id: NodeId,
}, },
ToggleHidden, ToggleHidden,
ToggleHiddenImpl,
TogglePreview { TogglePreview {
node_id: NodeId, node_id: NodeId,
}, },
TogglePreviewImpl {
node_id: NodeId,
},
} }

View File

@ -125,6 +125,16 @@ impl NodeGraphMessageHandler {
}) })
} }
/// Get the active graph_craft NodeNetwork struct
fn get_active_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> {
let mut network = self.get_root_network(document);
for segement in &self.nested_path {
network = network.and_then(|network| network.nodes.get(segement)).and_then(|node| node.implementation.get_network());
}
network
}
/// Get the active graph_craft NodeNetwork struct /// Get the active graph_craft NodeNetwork struct
fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> { fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> {
let mut network = self.get_root_network_mut(document); let mut network = self.get_root_network_mut(document);
@ -179,7 +189,7 @@ impl NodeGraphMessageHandler {
/// Updates the buttons for disable and preview /// Updates the buttons for disable and preview
fn update_selection_action_buttons(&mut self, document: &mut Document, responses: &mut VecDeque<Message>) { fn update_selection_action_buttons(&mut self, document: &mut Document, responses: &mut VecDeque<Message>) {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
let mut widgets = Vec::new(); let mut widgets = Vec::new();
// Don't allow disabling input or output nodes // Don't allow disabling input or output nodes
@ -406,7 +416,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
if let Some(_old_layer_path) = self.layer_path.take() { if let Some(_old_layer_path) = self.layer_path.take() {
responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: false }.into()); responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: false }.into());
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()); responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
// TODO: Close UI and clean up old node graph
} }
} }
NodeGraphMessage::ConnectNodesByLink { NodeGraphMessage::ConnectNodesByLink {
@ -415,26 +424,30 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
input_node_connector_index, input_node_connector_index,
} => { } => {
log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}."); log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}.");
let node_id = input_node;
let Some(network) = self.get_active_network_mut(document) else { let Some(network) = self.get_active_network(document) else {
error!("No network"); error!("No network");
return; return;
}; };
let Some(input_node) = network.nodes.get_mut(&input_node) else { let Some(input_node) = network.nodes.get(&input_node) else {
error!("No to"); error!("No to");
return; return;
}; };
let Some((actual_index, _)) = input_node.inputs.iter().enumerate().filter(|input|input.1.is_exposed()).nth(input_node_connector_index) else { let Some((input_index, _)) = input_node.inputs.iter().enumerate().filter(|input|input.1.is_exposed()).nth(input_node_connector_index) else {
error!("Failed to find actual index of connector indes {input_node_connector_index} on node {input_node:#?}"); error!("Failed to find actual index of connector indes {input_node_connector_index} on node {input_node:#?}");
return; return;
}; };
input_node.inputs[actual_index] = NodeInput::Node(output_node);
Self::send_graph(network, responses); responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
let input = NodeInput::Node(output_node);
responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
responses.push_back(NodeGraphMessage::SendGraph.into());
} }
NodeGraphMessage::Copy => { NodeGraphMessage::Copy => {
let Some(network) = self.get_active_network_mut(document) else { let Some(network) = self.get_active_network(document) else {
error!("No network"); error!("No network");
return; return;
}; };
@ -451,10 +464,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
NodeGraphMessage::CreateNode { node_id, node_type, x, y } => { NodeGraphMessage::CreateNode { node_id, node_type, x, y } => {
let node_id = node_id.unwrap_or_else(crate::application::generate_uuid); let node_id = node_id.unwrap_or_else(crate::application::generate_uuid);
let Some(network) = self.get_active_network_mut(document) else{
warn!("No network");
return;
};
let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else { let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else {
responses.push_back(DialogMessage::DisplayDialogError { title: "Cannot insert node".to_string(), description: format!("The document node '{node_type}' does not exist in the document node list") }.into()); responses.push_back(DialogMessage::DisplayDialogError { title: "Cannot insert node".to_string(), description: format!("The document node '{node_type}' does not exist in the document node list") }.into());
@ -481,17 +490,18 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
..Default::default() ..Default::default()
}; };
network.nodes.insert( responses.push_back(DocumentMessage::StartTransaction.into());
node_id,
DocumentNode { let document_node = DocumentNode {
name: node_type.clone(), name: node_type.clone(),
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(), inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
// TODO: Allow inserting nodes that contain other nodes. // TODO: Allow inserting nodes that contain other nodes.
implementation: DocumentNodeImplementation::Network(inner_network), implementation: DocumentNodeImplementation::Network(inner_network),
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y).into() }, metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y).into() },
}, };
); responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into());
Self::send_graph(network, responses);
responses.push_back(NodeGraphMessage::SendGraph.into());
} }
NodeGraphMessage::Cut => { NodeGraphMessage::Cut => {
responses.push_back(NodeGraphMessage::Copy.into()); responses.push_back(NodeGraphMessage::Copy.into());
@ -499,32 +509,26 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
NodeGraphMessage::DeleteNode { node_id } => { NodeGraphMessage::DeleteNode { node_id } => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network_mut(document) {
if self.remove_node(network, node_id) { self.remove_node(network, node_id);
Self::send_graph(network, responses);
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
}
} }
self.update_selected(document, responses); self.update_selected(document, responses);
} }
NodeGraphMessage::DeleteSelectedNodes => { NodeGraphMessage::DeleteSelectedNodes => {
if let Some(network) = self.get_active_network_mut(document) { responses.push_back(DocumentMessage::StartTransaction.into());
let mut modified = false;
for node_id in self.selected_nodes.clone() { for node_id in self.selected_nodes.clone() {
modified = modified || self.remove_node(network, node_id); responses.push_back(NodeGraphMessage::DeleteNode { node_id }.into());
} }
if modified {
Self::send_graph(network, responses); responses.push_back(NodeGraphMessage::SendGraph.into());
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
} }
}
self.update_selected(document, responses);
}
NodeGraphMessage::DisconnectNodes { node_id, input_index } => { NodeGraphMessage::DisconnectNodes { node_id, input_index } => {
let Some(network) = self.get_active_network_mut(document) else { let Some(network) = self.get_active_network(document) else {
warn!("No network"); warn!("No network");
return; return;
}; };
let Some(node) = network.nodes.get_mut(&node_id) else { let Some(node) = network.nodes.get(&node_id) else {
warn!("Invalid node"); warn!("Invalid node");
return; return;
}; };
@ -532,37 +536,46 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
warn!("Node {} not in library", node.name); warn!("Node {} not in library", node.name);
return; return;
}; };
node.inputs[input_index] = node_type.inputs[input_index].default.clone();
Self::send_graph(network, responses); responses.push_back(DocumentMessage::StartTransaction.into());
let input = node_type.inputs[input_index].default.clone();
responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
responses.push_back(NodeGraphMessage::SendGraph.into());
} }
NodeGraphMessage::DoubleClickNode { node } => { NodeGraphMessage::DoubleClickNode { node } => {
self.selected_nodes.clear(); if let Some(network) = self.get_active_network(document) {
if let Some(network) = self.get_active_network_mut(document) {
if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() { if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() {
self.nested_path.push(node); self.nested_path.push(node);
} }
} }
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
Self::send_graph(network, responses); Self::send_graph(network, responses);
} }
self.collect_nested_addresses(document, responses); self.collect_nested_addresses(document, responses);
self.update_selected(document, responses); self.update_selected(document, responses);
} }
NodeGraphMessage::DuplicateSelectedNodes => { NodeGraphMessage::DuplicateSelectedNodes => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
responses.push_back(DocumentMessage::StartTransaction.into());
let new_ids = &self.selected_nodes.iter().map(|&id| (id, crate::application::generate_uuid())).collect(); let new_ids = &self.selected_nodes.iter().map(|&id| (id, crate::application::generate_uuid())).collect();
self.selected_nodes.clear(); self.selected_nodes.clear();
// Copy the selected nodes // Copy the selected nodes
let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>(); let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>();
for (new_id, mut node) in copied_nodes { for (node_id, mut document_node) in copied_nodes {
// Shift duplicated node // Shift duplicated node
node.metadata.position += IVec2::splat(2); document_node.metadata.position += IVec2::splat(2);
// Add new node to the list // Add new node to the list
self.selected_nodes.push(new_id); self.selected_nodes.push(node_id);
network.nodes.insert(new_id, node);
// Insert new node into graph
responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into());
} }
Self::send_graph(network, responses); Self::send_graph(network, responses);
self.update_selected(document, responses); self.update_selected(document, responses);
} }
@ -572,36 +585,46 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
for _ in 0..depth_of_nesting { for _ in 0..depth_of_nesting {
self.nested_path.pop(); self.nested_path.pop();
} }
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
Self::send_graph(network, responses); Self::send_graph(network, responses);
} }
self.collect_nested_addresses(document, responses); self.collect_nested_addresses(document, responses);
self.update_selected(document, responses); self.update_selected(document, responses);
} }
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => { NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
let Some(network) = self.get_active_network_mut(document) else{ let Some(network) = self.get_active_network(document) else {
warn!("No network"); warn!("No network");
return; return;
}; };
let Some(node) = network.nodes.get_mut(&node_id) else { let Some(node) = network.nodes.get(&node_id) else {
warn!("No node"); warn!("No node");
return; return;
}; };
if let NodeInput::Value { exposed, .. } = &mut node.inputs[input_index] { responses.push_back(DocumentMessage::StartTransaction.into());
let mut input = node.inputs[input_index].clone();
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = new_exposed; *exposed = new_exposed;
} else if let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) { } else if let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) {
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default { if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
node.inputs[input_index] = NodeInput::Value { input = NodeInput::Value {
tagged_value: tagged_value.clone(), tagged_value: tagged_value.clone(),
exposed: new_exposed, exposed: new_exposed,
}; };
} }
} }
Self::send_graph(network, responses); responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
responses.push_back(NodeGraphMessage::SendGraph.into());
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()); responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
} }
NodeGraphMessage::InsertNode { node_id, document_node } => {
if let Some(network) = self.get_active_network_mut(document) {
network.nodes.insert(node_id, document_node);
}
}
NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => { NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => {
let Some(network) = self.get_active_network_mut(document) else { let Some(network) = self.get_active_network_mut(document) else {
warn!("No network"); warn!("No network");
@ -621,11 +644,9 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
return; return;
} }
if let Some(_old_layer_path) = self.layer_path.replace(layer_path) { self.layer_path = Some(layer_path);
// TODO: Necessary cleanup of old node graph
}
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
self.selected_nodes.clear(); self.selected_nodes.clear();
responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: true }.into()); responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: true }.into());
@ -638,7 +659,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
self.update_selected(document, responses); self.update_selected(document, responses);
} }
NodeGraphMessage::PasteNodes { serialized_nodes } => { NodeGraphMessage::PasteNodes { serialized_nodes } => {
let Some(network) = self.get_active_network_mut(document) else{ let Some(network) = self.get_active_network(document) else {
warn!("No network"); warn!("No network");
return; return;
}; };
@ -660,31 +681,38 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
shift += IVec2::splat(2); shift += IVec2::splat(2);
} }
self.selected_nodes.clear(); responses.push_back(DocumentMessage::StartTransaction.into());
let new_ids: HashMap<_, _> = data.iter().map(|&(id, _)| (id, crate::application::generate_uuid())).collect(); let new_ids: HashMap<_, _> = data.iter().map(|&(id, _)| (id, crate::application::generate_uuid())).collect();
for (old_id, mut node) in data { for (old_id, mut document_node) in data {
// Shift copied node // Shift copied node
node.metadata.position += shift; document_node.metadata.position += shift;
// Get the new, non-conflicting id // Get the new, non-conflicting id
let new_id = *new_ids.get(&old_id).unwrap(); let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(Self::default_node_input, &new_ids);
// Insert node into network // Insert node into network
network.nodes.insert(new_id, node.map_ids(Self::default_node_input, &new_ids)); responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into());
// Select the newly pasted node
self.selected_nodes.push(new_id);
} }
Self::send_graph(network, responses); let nodes = new_ids.values().copied().collect();
self.update_selected(document, responses); responses.push_back(NodeGraphMessage::SelectNodes { nodes }.into());
responses.push_back(NodeGraphMessage::SendGraph.into());
} }
NodeGraphMessage::SelectNodes { nodes } => { NodeGraphMessage::SelectNodes { nodes } => {
self.selected_nodes = nodes; self.selected_nodes = nodes;
self.update_selection_action_buttons(document, responses); self.update_selection_action_buttons(document, responses);
self.update_selected(document, responses);
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()); responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
} }
NodeGraphMessage::SendGraph => {
if let Some(network) = self.get_active_network(document) {
Self::send_graph(network, responses);
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
}
}
NodeGraphMessage::SetDrawing { new_drawing } => { NodeGraphMessage::SetDrawing { new_drawing } => {
let selected: Vec<_> = selected.collect(); let selected: Vec<_> = selected.collect();
// Check if we stopped drawing a node graph frame // Check if we stopped drawing a node graph frame
@ -704,14 +732,13 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
self.is_drawing_node_graph_frame = new_drawing self.is_drawing_node_graph_frame = new_drawing
} }
NodeGraphMessage::SetInputValue { node, input_index, value } => { NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network(document) {
if let Some(node) = network.nodes.get_mut(&node) { if let Some(node) = network.nodes.get(&node_id) {
// Extend number of inputs if not already large enough responses.push_back(DocumentMessage::StartTransaction.into());
if input_index >= node.inputs.len() {
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network)); let input = NodeInput::Value { tagged_value: value, exposed: false };
} responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false };
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()); responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
if node.name != "Imaginate" || input_index == 0 { if node.name != "Imaginate" || input_index == 0 {
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
@ -719,6 +746,13 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
} }
} }
} }
NodeGraphMessage::SetNodeInput { node_id, input_index, input } => {
if let Some(network) = self.get_active_network_mut(document) {
if let Some(node) = network.nodes.get_mut(&node_id) {
node.inputs[input_index] = input
}
}
}
NodeGraphMessage::SetQualifiedInputValue { NodeGraphMessage::SetQualifiedInputValue {
layer_path, layer_path,
node_path, node_path,
@ -794,9 +828,13 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
stack.extend(outwards_links.get(&id).unwrap_or(&Vec::new()).iter().copied()) stack.extend(outwards_links.get(&id).unwrap_or(&Vec::new()).iter().copied())
} }
} }
Self::send_graph(network, responses); responses.push_back(NodeGraphMessage::SendGraph.into());
} }
NodeGraphMessage::ToggleHidden => { NodeGraphMessage::ToggleHidden => {
responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(NodeGraphMessage::ToggleHiddenImpl.into());
}
NodeGraphMessage::ToggleHiddenImpl => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network_mut(document) {
// Check if any of the selected nodes are hidden // Check if any of the selected nodes are hidden
if self.selected_nodes.iter().any(|id| network.disabled.contains(id)) { if self.selected_nodes.iter().any(|id| network.disabled.contains(id)) {
@ -813,6 +851,10 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
} }
NodeGraphMessage::TogglePreview { node_id } => { NodeGraphMessage::TogglePreview { node_id } => {
responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(NodeGraphMessage::TogglePreviewImpl { node_id }.into());
}
NodeGraphMessage::TogglePreviewImpl { node_id } => {
if let Some(network) = self.get_active_network_mut(document) { if let Some(network) = self.get_active_network_mut(document) {
// Check if the node is not already // Check if the node is not already
if network.output != node_id { if network.output != node_id {

View File

@ -21,7 +21,7 @@ pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
fn update_value<T, F: Fn(&T) -> TaggedValue + 'static + Send + Sync>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> { fn update_value<T, F: Fn(&T) -> TaggedValue + 'static + Send + Sync>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> {
WidgetCallback::new(move |input_value: &T| { WidgetCallback::new(move |input_value: &T| {
NodeGraphMessage::SetInputValue { NodeGraphMessage::SetInputValue {
node: node_id, node_id,
input_index, input_index,
value: value(input_value), value: value(input_value),
} }

View File

@ -92,7 +92,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
ModifyFont { font_family, font_style, size } => { ModifyFont { font_family, font_style, size } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size })); self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size }, true, responses);
responses.push_back(ResendActiveProperties.into()); responses.push_back(ResendActiveProperties.into());
} }
ModifyTransform { value, transform_op } => { ModifyTransform { value, transform_op } => {
@ -101,33 +101,34 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let transform = apply_transform_operation(layer, transform_op, value, &persistent_data.font_cache); let transform = apply_transform_operation(layer, transform_op, value, &persistent_data.font_cache);
responses.push_back(self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform })); self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform }, true, responses);
} }
ModifyName { name } => { ModifyName { name } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name })) self.create_document_operation(Operation::SetLayerName { path, name }, true, responses);
} }
ModifyPreserveAspect { preserve_aspect } => { ModifyPreserveAspect { preserve_aspect } => {
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerPreserveAspect { layer_path, preserve_aspect })) self.create_document_operation(Operation::SetLayerPreserveAspect { layer_path, preserve_aspect }, true, responses);
} }
ModifyFill { fill } => { ModifyFill { fill } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill })); self.create_document_operation(Operation::SetLayerFill { path, fill }, true, responses);
} }
ModifyStroke { stroke } => { ModifyStroke { stroke } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke })) self.create_document_operation(Operation::SetLayerStroke { path, stroke }, true, responses);
} }
ModifyText { new_text } => { ModifyText { new_text } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(Operation::SetTextContent { path, new_text }.into()) self.create_document_operation(Operation::SetTextContent { path, new_text }, true, responses);
} }
SetPivot { new_position } => { SetPivot { new_position } => {
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let position: Option<glam::DVec2> = new_position.into(); let position: Option<glam::DVec2> = new_position.into();
let pivot = position.unwrap().into(); let pivot = position.unwrap().into();
responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(Operation::SetPivot { layer_path, pivot }.into()); responses.push_back(Operation::SetPivot { layer_path, pivot }.into());
} }
CheckSelectedWasUpdated { path } => { CheckSelectedWasUpdated { path } => {
@ -187,11 +188,24 @@ impl PropertiesPanelMessageHandler {
matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last) matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
} }
fn create_document_operation(&self, operation: Operation) -> Message { fn create_document_operation(&self, operation: Operation, commit_history: bool, responses: &mut VecDeque<Message>) {
let (_, target_document) = self.active_selection.as_ref().unwrap(); let (_, target_document) = self.active_selection.as_ref().unwrap();
match *target_document { match *target_document {
TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(), TargetDocument::Artboard => {
TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(), // Commit history is not respected as the artboard document is not saved in the history system.
// Dispatch the relevant operation to the artboard document
responses.push_back(ArtboardMessage::DispatchOperation(Box::new(operation)).into())
}
TargetDocument::Artwork => {
// Commit to history before the modification
if commit_history {
responses.push_back(DocumentMessage::StartTransaction.into());
}
// Dispatch the relevant operation to the main document
responses.push_back(DocumentMessage::DispatchOperation(Box::new(operation)).into());
}
} }
} }
} }

View File

@ -100,8 +100,8 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
// Send the DocumentIsDirty message to the active tool's sub-tool message handler // Send the DocumentIsDirty message to the active tool's sub-tool message handler
responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(BroadcastEvent::DocumentIsDirty.into());
// Send Properties to the frontend // Send tool options to the frontend
tool_data.tools.get(&tool_type).unwrap().register_properties(responses, LayoutTarget::ToolOptions); responses.push_back(ToolMessage::RefreshToolOptions.into());
// Notify the frontend about the new active tool to be displayed // Notify the frontend about the new active tool to be displayed
tool_data.register_properties(responses, LayoutTarget::ToolShelf); tool_data.register_properties(responses, LayoutTarget::ToolShelf);

View File

@ -473,11 +473,15 @@ impl Fsm for SelectToolFsmState {
// If the user clicks on new shape, make that layer their new selection. // If the user clicks on new shape, make that layer their new selection.
// Otherwise enter the box select mode // Otherwise enter the box select mode
let state = if tool_data.pivot.is_over(input.mouse.position) { let state = if tool_data.pivot.is_over(input.mouse.position) {
responses.push_back(DocumentMessage::StartTransaction.into());
tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]);
DraggingPivot DraggingPivot
} else if let Some(selected_edges) = dragging_bounds { } else if let Some(selected_edges) = dragging_bounds {
responses.push_back(DocumentMessage::StartTransaction.into());
let snap_x = selected_edges.2 || selected_edges.3; let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1; let snap_y = selected_edges.0 || selected_edges.1;
@ -498,6 +502,8 @@ impl Fsm for SelectToolFsmState {
ResizingBounds ResizingBounds
} else if rotating_bounds { } else if rotating_bounds {
responses.push_back(DocumentMessage::StartTransaction.into());
if let Some(bounds) = &mut tool_data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let selected = selected.iter().collect::<Vec<_>>(); let selected = selected.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.document_legacy); let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.document_legacy);
@ -679,6 +685,12 @@ impl Fsm for SelectToolFsmState {
Ready Ready
} }
(ResizingBounds, DragStop) => { (ResizingBounds, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
@ -688,6 +700,12 @@ impl Fsm for SelectToolFsmState {
Ready Ready
} }
(RotatingBounds, DragStop) => { (RotatingBounds, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
if let Some(bounds) = &mut tool_data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
@ -695,6 +713,12 @@ impl Fsm for SelectToolFsmState {
Ready Ready
} }
(DraggingPivot, DragStop) => { (DraggingPivot, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
Ready Ready
@ -769,6 +793,8 @@ impl Fsm for SelectToolFsmState {
self self
} }
(_, SetPivot { position }) => { (_, SetPivot { position }) => {
responses.push_back(DocumentMessage::StartTransaction.into());
let pos: Option<DVec2> = position.into(); let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, font_cache, responses); tool_data.pivot.set_normalized_position(pos.unwrap(), document, font_cache, responses);

View File

@ -210,10 +210,18 @@ impl Default for TextToolFsmState {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct TextToolData { struct TextToolData {
path: Vec<LayerId>, layer_path: Vec<LayerId>,
overlays: Vec<Vec<LayerId>>, overlays: Vec<Vec<LayerId>>,
} }
impl TextToolData {
/// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, responses: &mut VecDeque<Message>) {
let path = self.layer_path.clone();
responses.push_back(DocumentMessage::SetTextboxEditability { path, editable }.into());
}
}
fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] { fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
DAffine2::from_scale_angle_translation((pos2 - pos1).round(), 0., pos1.round() - DVec2::splat(0.5)).to_cols_array() DAffine2::from_scale_angle_translation((pos2 - pos1).round(), 0., pos1.round() - DVec2::splat(0.5)).to_cols_array()
} }
@ -293,55 +301,44 @@ impl Fsm for TextToolFsmState {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
let new_state = if let Some(l) = document // Check if the user has selected an existing text layer
let new_state = if let Some(clicked_text_layer_path) = document
.document_legacy .document_legacy
.intersects_quad_root(quad, font_cache) .intersects_quad_root(quad, font_cache)
.last() .last()
.filter(|l| document.document_legacy.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false)) .filter(|l| document.document_legacy.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false))
// Editing existing text
{ {
if state == TextToolFsmState::Editing { if state == TextToolFsmState::Editing {
responses.push_back( tool_data.set_editing(false, responses);
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
} }
tool_data.path = l.clone(); tool_data.layer_path = clicked_text_layer_path.clone();
responses.push_back( responses.push_back(DocumentMessage::StartTransaction.into());
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(), tool_data.set_editing(true, responses);
editable: true,
} let replacement_selected_layers = vec![tool_data.layer_path.clone()];
.into(), responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
);
responses.push_back(
DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![tool_data.path.clone()],
}
.into(),
);
Editing Editing
} }
// Creating new text // Create new text
else if state == TextToolFsmState::Ready { else if state == TextToolFsmState::Ready {
responses.push_back(DocumentMessage::StartTransaction.into());
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array(); let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
let font_size = tool_options.font_size; let font_size = tool_options.font_size;
let font_name = tool_options.font_name.clone(); let font_name = tool_options.font_name.clone();
let font_style = tool_options.font_style.clone(); let font_style = tool_options.font_style.clone();
tool_data.path = document.get_path_for_new_layer(); tool_data.layer_path = document.get_path_for_new_layer();
responses.push_back( responses.push_back(
Operation::AddText { Operation::AddText {
path: tool_data.path.clone(), path: tool_data.layer_path.clone(),
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
insert_index: -1, insert_index: -1,
text: r#""#.to_string(), text: String::new(),
style: style::PathStyle::new(None, Fill::solid(global_tool_data.primary_color)), style: style::PathStyle::new(None, Fill::solid(global_tool_data.primary_color)),
size: font_size as f64, size: font_size as f64,
font_name, font_name,
@ -351,37 +348,22 @@ impl Fsm for TextToolFsmState {
); );
responses.push_back( responses.push_back(
Operation::SetLayerTransformInViewport { Operation::SetLayerTransformInViewport {
path: tool_data.path.clone(), path: tool_data.layer_path.clone(),
transform, transform,
} }
.into(), .into(),
); );
responses.push_back( tool_data.set_editing(true, responses);
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: true,
}
.into(),
);
responses.push_back( let replacement_selected_layers = vec![tool_data.layer_path.clone()];
DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![tool_data.path.clone()], responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
}
.into(),
);
Editing Editing
} else { } else {
// Removing old text as editable // Removing old text as editable
responses.push_back( tool_data.set_editing(false, responses);
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
resize_overlays(&mut tool_data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
@ -392,13 +374,7 @@ impl Fsm for TextToolFsmState {
} }
(state, Abort) => { (state, Abort) => {
if state == TextToolFsmState::Editing { if state == TextToolFsmState::Editing {
responses.push_back( tool_data.set_editing(false, responses);
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
} }
resize_overlays(&mut tool_data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
@ -411,21 +387,10 @@ impl Fsm for TextToolFsmState {
Editing Editing
} }
(Editing, TextChange { new_text }) => { (Editing, TextChange { new_text }) => {
responses.push_back( let path = tool_data.layer_path.clone();
Operation::SetTextContent { responses.push_back(Operation::SetTextContent { path, new_text }.into());
path: tool_data.path.clone(),
new_text,
}
.into(),
);
responses.push_back( tool_data.set_editing(false, responses);
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
resize_overlays(&mut tool_data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
@ -433,10 +398,10 @@ impl Fsm for TextToolFsmState {
} }
(Editing, UpdateBounds { new_text }) => { (Editing, UpdateBounds { new_text }) => {
resize_overlays(&mut tool_data.overlays, responses, 1); resize_overlays(&mut tool_data.overlays, responses, 1);
let text = document.document_legacy.layer(&tool_data.path).unwrap().as_text().unwrap(); let text = document.document_legacy.layer(&tool_data.layer_path).unwrap().as_text().unwrap();
let quad = text.bounding_box(&new_text, text.load_face(font_cache)); let quad = text.bounding_box(&new_text, text.load_face(font_cache));
let transformed_quad = document.document_legacy.multiply_transforms(&tool_data.path).unwrap() * quad; let transformed_quad = document.document_legacy.multiply_transforms(&tool_data.layer_path).unwrap() * quad;
let bounds = transformed_quad.bounding_box(); let bounds = transformed_quad.bounding_box();
let operation = Operation::SetLayerTransformInViewport { let operation = Operation::SetLayerTransformInViewport {

View File

@ -593,11 +593,17 @@ export default defineComponent({
// Clicked on a node // Clicked on a node
if (nodeId) { if (nodeId) {
let modifiedSelected = false;
const id = BigInt(nodeId); const id = BigInt(nodeId);
if (e.shiftKey || e.ctrlKey) { if (e.shiftKey || e.ctrlKey) {
modifiedSelected = true;
if (this.selected.includes(id)) this.selected.splice(this.selected.lastIndexOf(id), 1); if (this.selected.includes(id)) this.selected.splice(this.selected.lastIndexOf(id), 1);
else this.selected.push(id); else this.selected.push(id);
} else if (!this.selected.includes(id)) { } else if (!this.selected.includes(id)) {
modifiedSelected = true;
this.selected = [id]; this.selected = [id];
} else { } else {
this.selectIfNotDragged = id; this.selectIfNotDragged = id;
@ -607,15 +613,17 @@ export default defineComponent({
this.draggingNodes = { startX: e.x, startY: e.y, roundX: 0, roundY: 0 }; this.draggingNodes = { startX: e.x, startY: e.y, roundX: 0, roundY: 0 };
} }
this.editor.instance.selectNodes(new BigUint64Array(this.selected)); if (modifiedSelected) this.editor.instance.selectNodes(new BigUint64Array(this.selected));
return; return;
} }
// Clicked on the graph background // Clicked on the graph background
this.panning = true; this.panning = true;
if (this.selected.length !== 0) {
this.selected = []; this.selected = [];
this.editor.instance.selectNodes(new BigUint64Array(this.selected)); this.editor.instance.selectNodes(new BigUint64Array(this.selected));
}
}, },
doubleClick(e: MouseEvent) { doubleClick(e: MouseEvent) {
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
@ -677,11 +685,13 @@ export default defineComponent({
} }
} else if (this.draggingNodes) { } else if (this.draggingNodes) {
if (this.draggingNodes.startX === e.x || this.draggingNodes.startY === e.y) { if (this.draggingNodes.startX === e.x || this.draggingNodes.startY === e.y) {
if (this.selectIfNotDragged !== undefined) { if (this.selectIfNotDragged !== undefined && (this.selected.length !== 1 || this.selected[0] !== this.selectIfNotDragged)) {
this.selected = [this.selectIfNotDragged]; this.selected = [this.selectIfNotDragged];
this.editor.instance.selectNodes(new BigUint64Array(this.selected)); this.editor.instance.selectNodes(new BigUint64Array(this.selected));
} }
} }
if (this.selected.length > 0 && this.draggingNodes.roundX !== 0 && this.draggingNodes.roundY !== 0)
this.editor.instance.moveSelectedNodes(this.draggingNodes.roundX, this.draggingNodes.roundY); this.editor.instance.moveSelectedNodes(this.draggingNodes.roundX, this.draggingNodes.roundY);
// Check if this node should be inserted between two other nodes // Check if this node should be inserted between two other nodes

View File

@ -644,6 +644,9 @@ impl JsEditorHandle {
/// Notifies the backend that the selected nodes have been moved /// Notifies the backend that the selected nodes have been moved
#[wasm_bindgen(js_name = moveSelectedNodes)] #[wasm_bindgen(js_name = moveSelectedNodes)]
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) { pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) {
let message = DocumentMessage::StartTransaction;
self.dispatch(message);
let message = NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y }; let message = NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y };
self.dispatch(message); self.dispatch(message);
} }