Code cleanup after migrating node graph interaction to the backend (#1790)

* Cleanup transforms and click targets

* Improve click target caching

* Fix click target bugs

* Add auto panning

* update_modified_click_targets

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-06-20 23:11:41 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 6fd5b76430
commit 84ac2e274e
15 changed files with 332 additions and 495 deletions
@@ -100,6 +100,7 @@ pub enum DocumentMessage {
},
RenderRulers,
RenderScrollbars,
ResetTransform,
SaveDocument,
SelectAllLayers,
SelectedLayersLower,
@@ -31,8 +31,6 @@ use graphene_std::vector::style::{Fill, FillType, Gradient};
use glam::{DAffine2, DVec2, IVec2};
use std::vec;
pub struct DocumentMessageData<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
@@ -74,9 +72,7 @@ pub struct DocumentMessageHandler {
/// We save this to provide a hint about which version of the editor was used to create the document.
commit_hash: String,
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
pub navigation: PTZ,
/// The current pan, and zoom state of the viewport's view of the node graph.
node_graph_transform: PTZ,
pub document_ptz: PTZ,
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
document_mode: DocumentMode,
/// The current view mode that the user has set for rendering the document within the viewport.
@@ -119,6 +115,12 @@ pub struct DocumentMessageHandler {
/// This is updated frequently, whenever the information it's derived from changes.
#[serde(skip)]
pub metadata: DocumentMetadata,
/// The current pan, and zoom state of the viewport's view of the node graph.
#[serde(skip)]
node_graph_ptz: HashMap<Vec<NodeId>, PTZ>,
/// Transform from node graph space to viewport space.
#[serde(skip)]
node_graph_to_viewport: HashMap<Vec<NodeId>, DAffine2>,
}
impl Default for DocumentMessageHandler {
@@ -139,8 +141,7 @@ impl Default for DocumentMessageHandler {
collapsed: CollapsedLayers::default(),
name: DEFAULT_DOCUMENT_NAME.to_string(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
navigation: PTZ::default(),
node_graph_transform: PTZ::default(),
document_ptz: PTZ::default(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
overlays_visible: true,
@@ -157,6 +158,8 @@ impl Default for DocumentMessageHandler {
undo_in_progress: false,
layer_range_selection_reference: None,
metadata: Default::default(),
node_graph_ptz: HashMap::new(),
node_graph_to_viewport: HashMap::new(),
}
}
}
@@ -181,10 +184,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} else {
self.selected_visible_layers_bounding_box_viewport()
},
ptz: if self.graph_view_overlay_open { &mut self.node_graph_transform } else { &mut self.navigation },
document_ptz: &mut self.document_ptz,
node_graph_ptz: &mut self.node_graph_ptz,
graph_view_overlay_open: self.graph_view_overlay_open,
document_network: &self.network,
node_graph_handler: &self.node_graph_handler,
node_graph_to_viewport: &self.node_graph_to_viewport.entry(self.node_graph_handler.network.clone()).or_insert(DAffine2::IDENTITY),
};
self.navigation_handler.process_message(message, responses, data);
@@ -218,6 +222,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
collapsed: &mut self.collapsed,
ipp,
graph_view_overlay_open: self.graph_view_overlay_open,
node_graph_to_viewport: &self.node_graph_to_viewport.entry(self.node_graph_handler.network.clone()).or_insert(DAffine2::IDENTITY),
},
);
}
@@ -378,7 +383,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::GraphViewOverlay { open } => {
self.graph_view_overlay_open = open;
// TODO: Find a better way to update click targets when undoing/redoing
// TODO: Update click targets when node graph is closed so that this is not necessary
if self.graph_view_overlay_open {
self.node_graph_handler.update_all_click_targets(&mut self.network, self.node_graph_handler.network.clone())
}
@@ -735,16 +740,15 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
DocumentMessage::RenderRulers => {
let document_transform_scale = self.navigation_handler.snapped_zoom(self.navigation.zoom);
let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom);
let ruler_origin = if !self.graph_view_overlay_open {
self.metadata().document_to_viewport.transform_point2(DVec2::ZERO)
} else {
let Some(network) = self.network.nested_network(&self.node_graph_handler.network) else {
log::error!("Nested network not found in UpdateDocumentTransform");
return;
};
network.node_graph_to_viewport.transform_point2(DVec2::ZERO)
self.node_graph_to_viewport
.get(&self.node_graph_handler.network)
.unwrap_or(&DAffine2::IDENTITY)
.transform_point2(DVec2::ZERO)
};
let log = document_transform_scale.log2();
let ruler_interval: f64 = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
@@ -758,7 +762,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
});
}
DocumentMessage::RenderScrollbars => {
let document_transform_scale = self.navigation_handler.snapped_zoom(self.navigation.zoom);
let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom);
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
@@ -767,7 +771,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let [bounds1, bounds2] = if !self.graph_view_overlay_open {
self.metadata().document_bounds_viewport_space().unwrap_or([viewport_mid; 2])
} else {
self.node_graph_handler.graph_bounds_viewport_space(&self.network).unwrap_or([viewport_mid; 2])
self.node_graph_to_viewport
.get(&self.node_graph_handler.network)
.and_then(|node_graph_to_viewport| self.node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport))
.unwrap_or([viewport_mid; 2])
};
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
@@ -1086,6 +1093,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::ResetTransform => {
let transform = if self.graph_view_overlay_open {
*self.node_graph_to_viewport.entry(self.node_graph_handler.network.clone()).or_insert(DAffine2::IDENTITY)
} else {
self.metadata.document_to_viewport
};
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
}
DocumentMessage::UpdateDocumentTransform { transform } => {
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
@@ -1095,11 +1110,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::RunDocumentGraph);
} else {
let Some(network) = self.network.nested_network_mut(&self.node_graph_handler.network) else {
log::error!("Nested network not found in UpdateDocumentTransform");
return;
};
network.node_graph_to_viewport = transform;
self.node_graph_to_viewport.insert(self.node_graph_handler.network.clone(), transform);
responses.add(FrontendMessage::UpdateNodeGraphTransform {
transform: Transform {
@@ -1120,10 +1131,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
DocumentMessage::ZoomCanvasToFitAll => {
let bounds = if self.graph_view_overlay_open {
self.node_graph_handler
.network_metadata
.get(&self.node_graph_handler.network)
.and_then(|network_metadata| network_metadata.bounding_box_subpath.as_ref().and_then(|subpath| subpath.bounding_box()))
self.node_graph_handler.bounding_box_subpath.as_ref().and_then(|subpath| subpath.bounding_box())
} else {
self.metadata().document_bounds_document_space(true)
};
@@ -1274,17 +1282,15 @@ impl DocumentMessageHandler {
self.selected_nodes
.selected_nodes(network)
.filter_map(|node| {
let mut node_path = self.node_graph_handler.network.clone();
node_path.push(*node);
let Some(node_metadata) = self.node_graph_handler.node_metadata.get(&node_path) else {
let Some(node_metadata) = self.node_graph_handler.node_metadata.get(&node) else {
log::debug!("Could not get click target for node {node}");
return None;
};
let Some(network_metadata) = self.node_graph_handler.network_metadata.get(&self.node_graph_handler.network) else {
log::debug!("Could not get network_metadata in selected_nodes_bounding_box_viewport");
let Some(node_graph_to_viewport) = self.node_graph_to_viewport.get(&self.node_graph_handler.network) else {
log::debug!("Could not get node_graph_to_viewport for network: {:?}", self.node_graph_handler.network);
return None;
};
node_metadata.node_click_target.subpath.bounding_box_with_transform(network_metadata.node_graph_to_viewport)
node_metadata.node_click_target.subpath.bounding_box_with_transform(*node_graph_to_viewport)
})
.reduce(graphene_core::renderer::Quad::combine_bounds)
}
@@ -1500,14 +1506,12 @@ impl DocumentMessageHandler {
pub fn undo_with_history(&mut self, responses: &mut VecDeque<Message>) {
let Some(previous_network) = self.undo(responses) else { return };
self.update_modified_click_targets(&previous_network);
self.document_redo_history.push_back(previous_network);
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_redo_history.pop_front();
}
// TODO: Find a better way to update click targets when undoing/redoing
if self.graph_view_overlay_open {
self.node_graph_handler.update_all_click_targets(&mut self.network, self.node_graph_handler.network.clone())
}
}
pub fn undo(&mut self, responses: &mut VecDeque<Message>) -> Option<NodeNetwork> {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
@@ -1520,6 +1524,18 @@ impl DocumentMessageHandler {
let previous_network = std::mem::replace(&mut self.network, network);
Some(previous_network)
}
pub fn redo_with_history(&mut self, responses: &mut VecDeque<Message>) {
// Push the UpdateOpenDocumentsList message to the queue in order to update the save status of the open documents
let Some(previous_network) = self.redo(responses) else { return };
self.update_modified_click_targets(&previous_network);
self.document_undo_history.push_back(previous_network);
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
}
pub fn redo(&mut self, responses: &mut VecDeque<Message>) -> Option<NodeNetwork> {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
@@ -1531,18 +1547,36 @@ impl DocumentMessageHandler {
let previous_network = std::mem::replace(&mut self.network, network);
Some(previous_network)
}
pub fn redo_with_history(&mut self, responses: &mut VecDeque<Message>) {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
let Some(previous_network) = self.redo(responses) else { return };
self.document_undo_history.push_back(previous_network);
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
// TODO: Find a better way to update click targets when undoing/redoing
if self.graph_view_overlay_open {
self.node_graph_handler.update_all_click_targets(&mut self.network, self.node_graph_handler.network.clone())
pub fn update_modified_click_targets(&mut self, previous_network: &NodeNetwork) {
// TODO: Cache nodes that were changed alongside every network in the undo/redo history, although this is complex since undoing to a previous state may change different nodes than redoing to the same state
let Some(previous_nested_network) = previous_network.nested_network(&self.node_graph_handler.network) else {
return;
};
let Some(network) = self.network.nested_network(&self.node_graph_handler.network) else {
log::error!("Could not get nested network in redo_with_history");
return;
};
for (node_id, current_node) in &network.nodes {
if let Some(previous_node) = previous_nested_network.nodes.get(&node_id) {
if previous_node.alias == current_node.alias
&& previous_node.inputs.iter().map(|node_input| node_input.is_exposed()).count() == current_node.inputs.iter().map(|node_input| node_input.is_exposed()).count()
&& previous_node.is_layer == current_node.is_layer
&& previous_node.metadata.position == current_node.metadata.position
{
continue;
}
}
self.node_graph_handler.update_click_target(*node_id, &self.network, self.node_graph_handler.network.clone());
}
self.node_graph_handler
.update_click_target(network.imports_metadata.0, &self.network, self.node_graph_handler.network.clone());
self.node_graph_handler
.update_click_target(network.exports_metadata.0, &self.network, self.node_graph_handler.network.clone());
}
pub fn current_hash(&self) -> Option<u64> {
@@ -1889,7 +1923,7 @@ impl DocumentMessageHandler {
.tooltip("Reset Tilt and Zoom to 100%")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
.on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into())
.disabled(self.navigation.tilt.abs() < 1e-4 && (self.navigation.zoom - 1.).abs() < 1e-4)
.disabled(self.document_ptz.tilt.abs() < 1e-4 && (self.document_ptz.zoom - 1.).abs() < 1e-4)
.widget_holder(),
PopoverButton::new()
.popover_layout(vec![
@@ -1920,7 +1954,7 @@ impl DocumentMessageHandler {
])
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.navigation.zoom) * 100.))
NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.document_ptz.zoom) * 100.))
.unit("%")
.min(0.000001)
.max(1000000.)
@@ -1937,7 +1971,7 @@ impl DocumentMessageHandler {
.widget_holder(),
];
let tilt_value = self.navigation_handler.snapped_tilt(self.navigation.tilt) / (std::f64::consts::PI / 180.);
let tilt_value = self.navigation_handler.snapped_tilt(self.document_ptz.tilt) / (std::f64::consts::PI / 180.);
if tilt_value.abs() > 0.00001 {
widgets.extend([
Separator::new(SeparatorType::Related).widget_holder(),
@@ -591,7 +591,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
if let Some(node) = modify_inputs.document_network.nodes.get_mut(&id) {
node.alias = alias.clone();
}
modify_inputs.node_graph.update_click_target(id, &modify_inputs.document_network, Vec::new());
let shift = nodes
.get(&NodeId(0))
@@ -615,6 +614,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// 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) {
@@ -702,6 +702,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
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);
@@ -11,17 +11,19 @@ use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graph_craft::document::NodeId;
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeNetwork;
pub struct NavigationMessageData<'a> {
pub metadata: &'a DocumentMetadata,
pub ipp: &'a InputPreprocessorMessageHandler,
pub selection_bounds: Option<[DVec2; 2]>,
pub ptz: &'a mut PTZ,
pub document_ptz: &'a mut PTZ,
pub node_graph_ptz: &'a mut HashMap<Vec<NodeId>, PTZ>,
pub graph_view_overlay_open: bool,
pub document_network: &'a NodeNetwork,
pub node_graph_handler: &'a NodeGraphMessageHandler,
pub node_graph_to_viewport: &'a DAffine2,
}
#[derive(Debug, Clone, PartialEq, Default)]
@@ -37,11 +39,17 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
metadata,
ipp,
selection_bounds,
ptz,
document_ptz,
node_graph_ptz,
graph_view_overlay_open,
document_network,
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())
};
let old_zoom = ptz.zoom;
match message {
@@ -112,10 +120,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let transformed_delta = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_vector2(delta)
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.inverse().transform_vector2(delta)
node_graph_to_viewport.inverse().transform_vector2(delta)
};
ptz.pan += transformed_delta;
@@ -126,10 +131,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let transformed_delta = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
node_graph_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
};
ptz.pan += transformed_delta;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
@@ -175,7 +177,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(document_network)
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
};
zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp);
@@ -187,7 +189,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(document_network)
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
};
ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
@@ -239,18 +241,12 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let v1 = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO)
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO)
node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO)
};
let v2 = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size())
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size())
node_graph_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size())
};
let center = ((v1 + v2) - (pos1 + pos2)) / 2.;
@@ -260,10 +256,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let viewport_change = if !graph_view_overlay_open {
metadata.document_to_viewport.transform_vector2(center)
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.transform_vector2(center)
node_graph_to_viewport.transform_vector2(center)
};
// Only change the pan if the change will be visible in the viewport
@@ -287,10 +280,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let transform = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse()
} else {
let Some(network) = document_network.nested_network(&node_graph_handler.network) else {
return;
};
network.node_graph_to_viewport.inverse()
node_graph_to_viewport.inverse()
};
responses.add(NavigationMessage::FitViewportToBounds {
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
@@ -344,7 +334,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(document_network)
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
};
updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp)
@@ -81,6 +81,9 @@ pub enum NodeGraphMessage {
shift: Key,
},
PointerUp,
PointerOutsideViewport {
shift: Key,
},
PrintSelectedNodeCoordinates,
RunDocumentGraph,
SelectedNodesAdd {
@@ -1,4 +1,4 @@
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath};
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, NodeMetadata, WirePath};
use super::{document_node_types, node_properties};
use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::macros::action_keys;
@@ -8,14 +8,15 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Modify
use crate::messages::portfolio::document::node_graph::document_node_types::NodePropertiesContext;
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, FrontendGraphDataType};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::node_metadata::{NetworkMetadata, NodeMetadata};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use bezier_rs::Subpath;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, FlowType, NodeId, NodeInput, NodeNetwork, Previewing, Source};
use graph_craft::proto::GraphErrors;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::*;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
@@ -33,6 +34,7 @@ pub struct NodeGraphHandlerData<'a> {
pub collapsed: &'a mut CollapsedLayers,
pub ipp: &'a InputPreprocessorMessageHandler,
pub graph_view_overlay_open: bool,
pub node_graph_to_viewport: &'a DAffine2,
}
#[derive(Debug, Clone)]
@@ -43,26 +45,25 @@ pub struct NodeGraphMessageHandler {
has_selection: bool,
widgets: [LayoutGroup; 2],
drag_start: Option<DragStart>,
// Used to add a transaction for the first node move when dragging
/// Used to add a transaction for the first node move when dragging.
begin_dragging: bool,
// Stored in pixel coordinates
/// Stored in pixel coordinates.
box_selection_start: Option<UVec2>,
disconnecting: Option<(NodeId, usize)>,
initial_disconnecting: bool,
// Node to select on pointer up if multiple nodes are selected and they were not dragged
/// Node to select on pointer up if multiple nodes are selected and they were not dragged.
select_if_not_dragged: Option<NodeId>,
// The start of the dragged line that cannot be moved. The bool represents if it is a vertical output.
/// The start of the dragged line that cannot be moved. The bool represents if it is a vertical output.
wire_in_progress_from_connector: Option<(DVec2, bool)>,
// The end point of the dragged line that can be moved. The bool represents if it is a vertical input.
/// The end point of the dragged line that can be moved. The bool represents if it is a vertical input.
wire_in_progress_to_connector: Option<(DVec2, bool)>,
// State for the context menu popups
/// State for the context menu popups.
context_menu: Option<ContextMenuInformation>,
/// Click targets for every node in every network by using the path to that node
/// TODO: Only store click targets for nodes in the current network
pub node_metadata: HashMap<Vec<NodeId>, NodeMetadata>,
// Bounding box around all nodes and the node graph to viewport transform for all networks
/// TODO: Only store network metadata for the network that is being viewed
pub network_metadata: HashMap<Vec<NodeId>, NetworkMetadata>,
/// Click targets for every node in the network by using the path to that node.
pub node_metadata: HashMap<NodeId, NodeMetadata>,
/// Cache for the bounding box around all nodes in node graph space.
pub bounding_box_subpath: Option<Subpath<ManipulatorGroupId>>,
auto_panning: AutoPanning,
}
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
@@ -76,6 +77,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
collapsed,
graph_view_overlay_open,
ipp,
node_graph_to_viewport,
..
} = data;
@@ -221,22 +223,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
if let Some(wire_in_progress) = self.wire_in_progress_from_connector {
let Some((from_node, output_index)) = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.output_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
wire_in_progress.0,
) else {
let Some((from_node, output_index)) = self.get_connector_from_point(wire_in_progress.0, |metadata| &metadata.output_click_targets) else {
log::error!("Could not get output form connector start");
return;
};
@@ -322,73 +309,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::EnterNestedNetwork => {
let Some(network) = document_network.nested_network_mut(&self.network) else {
return;
};
let viewport_location = ipp.mouse.position;
let point = network.node_graph_to_viewport.inverse().transform_point2(viewport_location);
let Some(node_id) = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter_map(|(path, node_metadata)| {
let mut path = path.clone();
let node_id = path.pop().expect("Path to node should not be empty");
if *path == self.network {
// TODO: There should be a way to do this without cloning
Some((node_id, node_metadata.node_click_target.clone()))
} else {
None
}
})
.collect::<HashMap<NodeId, ClickTarget>>(),
point,
) else {
return;
};
if NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter_map(|(path, node_metadata)| {
let mut path = path.clone();
let node_id = path.pop().expect("Path to node should not be empty");
if *path == self.network {
// TODO: There should be a way to do this without cloning
if let Some(visibility_click_target) = &node_metadata.visibility_click_target {
Some((node_id, visibility_click_target.clone()))
} else {
None
}
} else {
None
}
})
.collect::<HashMap<NodeId, ClickTarget>>(),
point,
)
.is_some()
{
return;
};
if network.imports_metadata.0 == node_id || network.exports_metadata.0 == node_id {
return;
}
let Some(node) = network.nodes.get_mut(&node_id) else {
return;
};
if let DocumentNodeImplementation::Network(_) = node.implementation {
self.network.push(node_id);
self.update_all_click_targets(document_network, self.network.clone());
responses.add(DocumentMessage::ZoomCanvasToFitAll);
}
self.send_graph(document_network, document_metadata, collapsed, graph_view_overlay_open, responses);
self.update_selected(document_network, selected_nodes, responses);
}
NodeGraphMessage::DuplicateSelectedNodes => {
if let Some(network) = document_network.nested_network_for_selected_nodes(&self.network, selected_nodes.selected_nodes_ref().iter()) {
responses.add(DocumentMessage::StartTransaction);
@@ -426,6 +346,35 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SetToNodeOrLayer { node_id: node_id, is_layer: false })
}
}
NodeGraphMessage::EnterNestedNetwork => {
let Some(network) = document_network.nested_network_mut(&self.network) else { return };
let viewport_location = ipp.mouse.position;
let point = node_graph_to_viewport.inverse().transform_point2(viewport_location);
let Some(node_id) = self.get_node_from_point(point) else { return };
if self.get_visibility_from_point(point).is_some() {
return;
};
if network.imports_metadata.0 == node_id || network.exports_metadata.0 == node_id {
return;
}
let Some(node) = network.nodes.get_mut(&node_id) else { return };
if let DocumentNodeImplementation::Network(_) = node.implementation {
self.network.push(node_id);
self.node_metadata.clear();
self.update_all_click_targets(document_network, self.network.clone());
responses.add(DocumentMessage::ZoomCanvasToFitAll);
}
responses.add(DocumentMessage::ResetTransform);
responses.add(NodeGraphMessage::SendGraph);
self.update_selected(document_network, selected_nodes, responses);
}
NodeGraphMessage::ExitNestedNetwork { steps_back } => {
selected_nodes.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
@@ -433,15 +382,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
for _ in 0..steps_back {
self.network.pop();
}
// TODO: Find a better way to update click targets when undoing/redoing
self.node_metadata.clear();
self.update_all_click_targets(document_network, self.network.clone());
let Some(network) = document_network.nested_network(&self.network) else {
return;
};
responses.add(DocumentMessage::UpdateDocumentTransform {
transform: network.node_graph_to_viewport,
});
responses.add(DocumentMessage::ResetTransform);
responses.add(NodeGraphMessage::SendGraph);
self.update_selected(document_network, selected_nodes, responses);
}
@@ -625,81 +568,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
};
let viewport_location = ipp.mouse.position;
let point = network.node_graph_to_viewport.inverse().transform_point2(viewport_location);
let point = node_graph_to_viewport.inverse().transform_point2(viewport_location);
if let Some(clicked_visibility) = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter_map(|(path, node_metadata)| {
let mut path = path.clone();
let node_id = path.pop().expect("Path to node should not be empty");
if *path == self.network {
// TODO: There should be a way to do this without cloning
if let Some(visibility_click_target) = &node_metadata.visibility_click_target {
Some((node_id, visibility_click_target.clone()))
} else {
None
}
} else {
None
}
})
.collect::<HashMap<NodeId, ClickTarget>>(),
point,
) {
if let Some(clicked_visibility) = self.get_visibility_from_point(point) {
responses.add(NodeGraphMessage::ToggleVisibility { node_id: clicked_visibility });
return;
}
let clicked_id = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter_map(|(path, node_metadata)| {
let mut path = path.clone();
let node_id = path.pop().expect("Path to node should not be empty");
if *path == self.network {
// TODO: There should be a way to do this without cloning
Some((node_id, node_metadata.node_click_target.clone()))
} else {
None
}
})
.collect::<HashMap<NodeId, ClickTarget>>(),
point,
);
let clicked_input = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.input_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
point,
);
let clicked_output = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.output_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
point,
);
let clicked_id = self.get_node_from_point(point);
let clicked_input = self.get_connector_from_point(point, |metadata| &metadata.input_click_targets);
let clicked_output = self.get_connector_from_point(point, |metadata| &metadata.output_click_targets);
// Create the add node popup on right click, then exit
if right_click {
@@ -716,11 +594,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_graph_shift = if matches!(context_menu_data, ContextMenuData::CreateNode) {
let appear_right_of_mouse = if viewport_location.x > ipp.viewport_bounds.size().x - 180. { -180. } else { 0. };
let appear_above_mouse = if viewport_location.y > ipp.viewport_bounds.size().y - 200. { -200. } else { 0. };
DVec2::new(appear_right_of_mouse, appear_above_mouse) / network.node_graph_to_viewport.matrix2.x_axis.x
DVec2::new(appear_right_of_mouse, appear_above_mouse) / node_graph_to_viewport.matrix2.x_axis.x
} else {
let appear_right_of_mouse = if viewport_location.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
let appear_above_mouse = if viewport_location.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
DVec2::new(appear_right_of_mouse, appear_above_mouse) / network.node_graph_to_viewport.matrix2.x_axis.x
DVec2::new(appear_right_of_mouse, appear_above_mouse) / node_graph_to_viewport.matrix2.x_axis.x
};
let context_menu_coordinates = ((point.x + node_graph_shift.x) as i32, (point.y + node_graph_shift.y) as i32);
@@ -739,9 +617,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// If the user is clicking on the create nodes list or context menu, break here
if let Some(context_menu) = &self.context_menu {
let context_menu_viewport = network
.node_graph_to_viewport
.transform_point2(DVec2::new(context_menu.context_menu_coordinates.0 as f64, context_menu.context_menu_coordinates.1 as f64));
let context_menu_viewport = node_graph_to_viewport.transform_point2(DVec2::new(context_menu.context_menu_coordinates.0 as f64, context_menu.context_menu_coordinates.1 as f64));
let (width, height) = if matches!(context_menu.context_menu_data, ContextMenuData::ToggleLayer { .. }) {
// Height and width for toggle layer menu
(173., 34.)
@@ -937,37 +813,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let Some(network) = document_network.nested_network(&self.network) else {
return;
};
// Auto-panning
let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];
self.auto_panning.setup_by_mouse_position(ipp, &messages, responses);
let viewport_location = ipp.mouse.position;
let point = network.node_graph_to_viewport.inverse().transform_point2(viewport_location);
let point = node_graph_to_viewport.inverse().transform_point2(viewport_location);
if self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none() {
if let Some((to_connector_node_position, is_layer, input_index)) = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.input_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
if let Some((to_connector_node_position, is_layer, input_index)) =
self.get_connector_from_point(point, |metadata| &metadata.input_click_targets).and_then(|(node_id, input_index)| {
network.nodes.get(&node_id).map(|node| (node.metadata.position, node.is_layer, input_index)).or_else(|| {
if node_id == network.exports_metadata.0 {
Some((network.exports_metadata.1 + IVec2::new(0, 1), false, input_index))
} else if node_id == network.imports_metadata.0 {
Some((network.imports_metadata.1 + IVec2::new(0, 1), false, input_index))
} else {
None
}
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
point,
)
.and_then(|(node_id, input_index)| {
network.nodes.get(&node_id).map(|node| (node.metadata.position, node.is_layer, input_index)).or_else(|| {
if node_id == network.exports_metadata.0 {
Some((network.exports_metadata.1 + IVec2::new(0, 1), false, input_index))
} else if node_id == network.imports_metadata.0 {
Some((network.imports_metadata.1 + IVec2::new(0, 1), false, input_index))
} else {
None
}
})
}) {
}) {
let to_connector_position = if is_layer {
if input_index == 0 {
DVec2::new(to_connector_node_position.x as f64 * 24. + 2. * 24., to_connector_node_position.y as f64 * 24. + 2. * 24. + 12.)
@@ -981,28 +847,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
)
};
self.wire_in_progress_to_connector = Some((to_connector_position, input_index == 0 && is_layer));
} else if let Some((to_connector_node_position, is_layer, output_index)) = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.output_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
point,
)
.and_then(|(node_id, output_index)| {
network
.nodes
.get(&node_id)
.map(|node| (node.metadata.position, node.is_layer, output_index + if node.has_primary_output { 0 } else { 1 }))
}) {
} else if let Some((to_connector_node_position, is_layer, output_index)) =
self.get_connector_from_point(point, |metadata| &metadata.output_click_targets).and_then(|(node_id, output_index)| {
network
.nodes
.get(&node_id)
.map(|node| (node.metadata.position, node.is_layer, output_index + if node.has_primary_output { 0 } else { 1 }))
}) {
let to_connector_position = if is_layer {
DVec2::new(to_connector_node_position.x as f64 * 24. + 2. * 24., to_connector_node_position.y as f64 * 24. - 12.)
} else {
@@ -1057,7 +908,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
drag_start.round_y = graph_delta.y;
}
} else if let Some(box_selection_start) = self.box_selection_start {
// TODO: Is this still an issue? The mouse button was released but we missed the pointer up event
// The mouse button was released but we missed the pointer up event
// if ((e.buttons & 1) === 0) {
// completeBoxSelection();
// boxSelection = undefined;
@@ -1069,11 +920,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let box_selection = Some(BoxSelection {
start_x: box_selection_start.x,
start_y: box_selection_start.y,
end_x: viewport_location.x.round().abs() as u32,
end_y: viewport_location.y.round().abs() as u32,
end_x: viewport_location.x.max(0.) as u32,
end_y: viewport_location.y.max(0.) as u32,
});
let graph_start = network.node_graph_to_viewport.inverse().transform_point2(box_selection_start.into());
let graph_start = node_graph_to_viewport.inverse().transform_point2(box_selection_start.into());
// TODO: Only loop through visible nodes
let shift = ipp.keyboard.get(shift as usize);
@@ -1084,11 +935,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
.map(|(node_id, _)| node_id)
.chain(vec![network.exports_metadata.0, network.imports_metadata.0].iter())
{
let mut node_id_path = self.network.clone();
node_id_path.push(*node_id);
if self
.node_metadata
.get(&node_id_path)
.get(&node_id)
.is_some_and(|node_metadata| node_metadata.node_click_target.intersect_rectangle(Quad::from_box([graph_start, point]), DAffine2::IDENTITY))
{
nodes.push(*node_id);
@@ -1105,42 +954,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
};
// Disconnect if the wire was previously connected to an input
let viewport_location = ipp.mouse.position;
let point = network.node_graph_to_viewport.inverse().transform_point2(viewport_location);
let point = node_graph_to_viewport.inverse().transform_point2(viewport_location);
if let (Some(wire_in_progress_from_connector), Some(wire_in_progress_to_connector)) = (self.wire_in_progress_from_connector, self.wire_in_progress_to_connector) {
// Check if dragged connector is reconnected to another input
let node_from = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.output_click_targets
.iter()
.enumerate()
.map(|(index, output_click_target)| ((*path.last().expect("Path should not be empty"), index), output_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
wire_in_progress_from_connector.0,
);
let node_to = NodeGraphMessageHandler::get_key_from_point(
&self
.node_metadata
.iter()
.filter(|(path, _)| path.starts_with(&self.network) && path.len() == self.network.len() + 1)
.flat_map(|(path, node_metadata)| {
// TODO: There should be a way to do this without cloning
node_metadata
.input_click_targets
.iter()
.enumerate()
.map(|(index, input_click_target)| ((*path.last().expect("Path should not be empty"), index), input_click_target.clone()))
})
.collect::<HashMap<(NodeId, usize), ClickTarget>>(),
wire_in_progress_to_connector.0,
);
let node_from = self.get_connector_from_point(wire_in_progress_from_connector.0, |metadata| &metadata.output_click_targets);
let node_to = self.get_connector_from_point(wire_in_progress_to_connector.0, |metadata| &metadata.input_click_targets);
if let (Some(node_from), Some(node_to)) = (node_from, node_to) {
responses.add(NodeGraphMessage::ConnectNodesByWire {
@@ -1157,7 +976,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let appear_right_of_mouse = if viewport_location.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
let appear_above_mouse = if viewport_location.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network.node_graph_to_viewport.matrix2.x_axis.x;
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / node_graph_to_viewport.matrix2.x_axis.x;
self.context_menu = Some(ContextMenuInformation {
context_menu_coordinates: ((point.x + node_graph_shift.x) as i32, (point.y + node_graph_shift.y) as i32),
@@ -1210,10 +1029,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// Check if primary output is disconnected
if !has_primary_output_connection {
// TODO: Cache all wire locations. This will be difficult since there are many ways for an input to changes, and each change will have to update the cache
let mut node_id_path = self.network.clone();
node_id_path.push(selected_node_id);
let Some(bounding_box) = self.node_metadata.get(&node_id_path).and_then(|node_metadata| node_metadata.node_click_target.subpath.bounding_box()) else {
log::error!("Could not get bounding box for node: {node_id_path:?}");
let Some(bounding_box) = self
.node_metadata
.get(&selected_node_id)
.and_then(|node_metadata| node_metadata.node_click_target.subpath.bounding_box())
else {
log::error!("Could not get bounding box for node: {selected_node_id}");
return;
};
let overlapping_wire = Self::collect_wires(network).into_iter().find(|frontend_wire| {
@@ -1278,7 +1099,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
responses.add(FrontendMessage::UpdateBox { box_selection: None })
}
NodeGraphMessage::PointerOutsideViewport { shift } => {
if self.drag_start.is_some() || self.box_selection_start.is_some() {
let _ = self.auto_panning.shift_viewport(ipp, responses);
} else {
// Auto-panning
let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];
self.auto_panning.stop(&messages, responses);
}
}
NodeGraphMessage::PrintSelectedNodeCoordinates => {
let Some(network) = document_network.nested_network_for_selected_nodes(&self.network, selected_nodes.selected_nodes_ref().iter()) else {
warn!("No network");
@@ -1569,6 +1398,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
};
if let Some(node) = network.nodes.get_mut(&node_id) {
node.alias = name;
if let Some(node_metadata) = self.node_metadata.get_mut(&node_id) {
if node.is_layer {
node_metadata.layer_width = Some(NodeGraphMessageHandler::layer_width_cells(node));
} else {
node_metadata.layer_width = None;
}
};
self.update_click_target(node_id, document_network, self.network.clone());
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
@@ -1758,6 +1594,18 @@ impl NodeGraphMessageHandler {
Some(text_width)
}
pub fn layer_width_cells(node: &DocumentNode) -> u32 {
let half_grid_cell_offset = 24. / 2.;
let thumbnail_width = 3. * 24.;
let gap_width = 8.;
let text_width = Self::get_text_width(node).unwrap_or_default();
let icon_width = 24.;
let icon_overhang_width = icon_width / 2.;
let text_right = half_grid_cell_offset + thumbnail_width + gap_width + text_width;
let layer_width_pixels = text_right + gap_width + icon_width - icon_overhang_width;
((layer_width_pixels / 24.) as u32).max(8)
}
// Inserts a node into the network and updates the click target
pub fn insert_node(&mut self, node_id: NodeId, node: DocumentNode, document_network: &mut NodeNetwork, network_path: &Vec<NodeId>) {
@@ -1780,26 +1628,16 @@ impl NodeGraphMessageHandler {
return;
};
let mut node_id_path = network_path.clone();
node_id_path.push(node_id);
// Clear all click targets for the node
self.node_metadata.remove(&node_id_path);
let grid_size = 24; // Number of pixels per grid unit at 100% zoom
if let Some(node) = network.nodes.get(&node_id) {
let mut layer_width = None;
let width = if node.is_layer {
let half_grid_cell_offset = 24. / 2.;
let thumbnail_width = 3. * 24.;
let gap_width = 8.;
let text_width = Self::get_text_width(node).unwrap_or_default();
let icon_width = 24.;
let icon_overhang_width = icon_width / 2.;
let text_right = half_grid_cell_offset + thumbnail_width + gap_width + text_width;
let layer_width_pixels = text_right + gap_width + icon_width - icon_overhang_width;
let layer_width_cells = (((layer_width_pixels) / 24.).ceil() as u32).max(8);
let layer_width_cells = self
.node_metadata
.get(&node_id)
.and_then(|node_metadata| node_metadata.layer_width)
.unwrap_or_else(|| Self::layer_width_cells(node));
layer_width = Some(layer_width_cells);
@@ -1916,7 +1754,7 @@ impl NodeGraphMessageHandler {
visibility_click_target,
layer_width,
};
self.node_metadata.insert(node_id_path.clone(), node_metadata);
self.node_metadata.insert(node_id, node_metadata);
} else if node_id == network.exports_metadata.0 {
let width = 5 * grid_size;
// 1 is added since the first row is reserved for the "Exports" name
@@ -1958,7 +1796,7 @@ impl NodeGraphMessageHandler {
layer_width: None,
};
self.node_metadata.insert(node_id_path.clone(), node_metadata);
self.node_metadata.insert(node_id, node_metadata);
}
// The number of imports is from the parent node, which is passed as a parameter. The number of exports is available from self.
else if node_id == network.imports_metadata.0 {
@@ -2010,51 +1848,17 @@ impl NodeGraphMessageHandler {
visibility_click_target,
layer_width: None,
};
self.node_metadata.insert(node_id_path.clone(), node_metadata);
}
}
// if node_click_target is outside the current bounding box, update the bounding box
if self.network_metadata.get(&network_path).map_or(true, |network_metadata| {
network_metadata.bounding_box_subpath.as_ref().map_or(true, |bounding_box| {
bounding_box.bounding_box().is_some_and(|bounding_box| {
self.node_metadata.get(&node_id_path).map_or(true, |node_metadata| {
node_metadata.node_click_target.subpath.bounding_box().is_some_and(|click_target| {
// Combine bounds and check if new vec is larger than current bounding box
let new_bounds = Quad::combine_bounds(click_target, bounding_box);
bounding_box != new_bounds
})
})
})
})
}) {
let node_id_path_len = node_id_path.len();
let bounds = self
.node_metadata
.iter()
.filter_map(|(node_path, node_metadata)| {
// Check if node is in same network as the updated node
if node_path.len() == node_id_path_len && node_path.starts_with(&network_path) {
node_metadata.node_click_target.subpath.bounding_box()
} else {
None
}
})
.reduce(Quad::combine_bounds);
let bounds = bounds.map(|bounds| bezier_rs::Subpath::new_rect(bounds[0], bounds[1]));
if let Some(network_metadata) = self.network_metadata.get_mut(&network_path) {
network_metadata.bounding_box_subpath = bounds;
} else {
self.network_metadata.insert(
network_path,
NetworkMetadata {
bounding_box_subpath: bounds,
node_graph_to_viewport: DAffine2::IDENTITY,
},
);
self.node_metadata.insert(node_id, node_metadata);
}
} else {
self.node_metadata.remove(&node_id);
}
let bounds = self
.node_metadata
.iter()
.filter_map(|(_, node_metadata)| node_metadata.node_click_target.subpath.bounding_box())
.reduce(Quad::combine_bounds);
self.bounding_box_subpath = bounds.map(|bounds| bezier_rs::Subpath::new_rect(bounds[0], bounds[1]));
}
// Updates all click targets in a certain network
@@ -2065,7 +1869,7 @@ impl NodeGraphMessageHandler {
};
let export_id = network.exports_metadata.0;
let import_id = network.imports_metadata.0;
for node_id in network.nodes.clone().keys() {
for (node_id, _) in network.nodes.iter() {
self.update_click_target(*node_id, document_network, network_path.clone());
}
self.update_click_target(export_id, document_network, network_path.clone());
@@ -2073,23 +1877,41 @@ impl NodeGraphMessageHandler {
}
/// Gets the bounding box in viewport coordinates for each node in the node graph
pub fn graph_bounds_viewport_space(&self, document_network: &NodeNetwork) -> Option<[DVec2; 2]> {
self.network_metadata.get(&self.network).and_then(|network_metadata| {
network_metadata.bounding_box_subpath.as_ref().and_then(|bounding_box| {
document_network
.nested_network(&self.network)
.and_then(|network| bounding_box.bounding_box_with_transform(network.node_graph_to_viewport))
})
})
pub fn graph_bounds_viewport_space(&self, node_graph_to_viewport: DAffine2) -> Option<[DVec2; 2]> {
self.bounding_box_subpath
.as_ref()
.and_then(|bounding_box| bounding_box.bounding_box_with_transform(node_graph_to_viewport))
}
/// Get the clicked target from a mouse click
fn get_key_from_point<T: Clone>(hashmap: &HashMap<T, ClickTarget>, point: DVec2) -> Option<T> {
hashmap
fn get_node_from_point(&self, point: DVec2) -> Option<NodeId> {
self.node_metadata
.iter()
.filter(move |(_, target)| target.intersect_point(point, DAffine2::IDENTITY))
.map(|(data, _)| data.clone())
.next()
.map(|(node_id, node_metadata)| (node_id, &node_metadata.node_click_target))
.find_map(|(node_id, click_target)| if click_target.intersect_point(point, DAffine2::IDENTITY) { Some(*node_id) } else { None })
}
fn get_connector_from_point<F>(&self, point: DVec2, click_target_selector: F) -> Option<(NodeId, usize)>
where
F: Fn(&NodeMetadata) -> &Vec<ClickTarget>,
{
self.node_metadata
.iter()
.map(|(node_id, node_metadata)| (node_id, click_target_selector(node_metadata)))
.find_map(|(node_id, click_targets)| {
for (index, click_target) in click_targets.iter().enumerate() {
if click_target.intersect_point(point, DAffine2::IDENTITY) {
return Some((node_id.clone(), index));
}
}
None
})
}
fn get_visibility_from_point(&self, point: DVec2) -> Option<NodeId> {
self.node_metadata
.iter()
.filter_map(|(node_id, node_metadata)| node_metadata.visibility_click_target.as_ref().map(|click_target| (node_id, click_target)))
.find_map(|(node_id, click_target)| if click_target.intersect_point(point, DAffine2::IDENTITY) { Some(*node_id) } else { None })
}
/// Send the cached layout to the frontend for the options bar at the top of the node panel
@@ -2510,13 +2332,14 @@ impl NodeGraphMessageHandler {
} else {
(FrontendGraphDataType::General, None)
}
}
// If type should only be determined from resolved_types then remove this
else if let NodeInput::Value { tagged_value, .. } = export {
} else if let NodeInput::Value { tagged_value, .. } = export {
(FrontendGraphDataType::with_type(&tagged_value.ty()), Some(tagged_value.ty()))
} else if let NodeInput::Network { import_type, .. } = export {
(FrontendGraphDataType::with_type(import_type), Some(import_type.clone()))
} else {
}
// TODO: Get type from parent node input when <https://github.com/GraphiteEditor/Graphite/issues/1762> is possible
// else if let NodeInput::Network { import_type, .. } = export {
// (FrontendGraphDataType::with_type(import_type), Some(import_type.clone()))
// }
else {
(FrontendGraphDataType::General, None)
};
@@ -2719,17 +2542,7 @@ impl NodeGraphMessageHandler {
let layer_widths = self
.node_metadata
.iter()
.filter_map(|(node_path, node_metadata)| {
if node_path.starts_with(&self.network) && node_path.len() == self.network.len() + 1 {
if let Some(layer_width) = node_metadata.layer_width {
Some((*node_path.last().unwrap(), layer_width))
} else {
None
}
} else {
None
}
})
.filter_map(|(node_id, node_metadata)| node_metadata.layer_width.map(|layer_width| (*node_id, layer_width)))
.collect::<HashMap<NodeId, u32>>();
responses.add(FrontendMessage::UpdateLayerWidths { layer_widths });
}
@@ -2767,8 +2580,14 @@ impl NodeGraphMessageHandler {
resolved_types.outputs.get(&Source { node: current_path.clone(), index: 0 }).cloned()
} else if let NodeInput::Value { tagged_value, .. } = current_export {
Some(tagged_value.ty())
} else if let NodeInput::Network { import_type, .. } = current_export {
Some(import_type.clone())
} else if let NodeInput::Network { import_index, .. } = current_export {
resolved_types
.outputs
.get(&Source {
node: node_id_path.clone(),
index: *import_index,
})
.cloned()
} else {
None
};
@@ -2973,7 +2792,8 @@ impl Default for NodeGraphMessageHandler {
wire_in_progress_to_connector: None,
context_menu: None,
node_metadata: HashMap::new(),
network_metadata: HashMap::new(),
bounding_box_subpath: None,
auto_panning: Default::default(),
}
}
}
@@ -1,6 +1,7 @@
use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graphene_core::Type;
use graphene_std::renderer::ClickTarget;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
@@ -166,3 +167,17 @@ pub struct ContextMenuInformation {
#[serde(rename = "contextMenuData")]
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>,
}
@@ -12,7 +12,7 @@ use graphene_std::vector::style::FillChoice;
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
let origin = document.snapping_state.grid.origin;
let grid_color: Color = document.snapping_state.grid.grid_color;
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.navigation) else {
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.metadata().document_to_viewport;
@@ -55,7 +55,7 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
fn grid_overlay_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
let origin = document.snapping_state.grid.origin;
let grid_color = document.snapping_state.grid.grid_color;
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.navigation) else {
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.metadata().document_to_viewport;
@@ -98,7 +98,7 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.navigation) else {
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {
return;
};
let isometric_spacing = spacing * spacing_multiplier;
@@ -150,7 +150,7 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.navigation) else {
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {
return;
};
let isometric_spacing = spacing * spacing_multiplier;
@@ -2,6 +2,5 @@ pub mod clipboards;
pub mod document_metadata;
pub mod error;
pub mod misc;
pub mod node_metadata;
pub mod nodes;
pub mod transformation;
@@ -1,26 +0,0 @@
use bezier_rs::Subpath;
use glam::DAffine2;
use graphene_core::renderer::ClickTarget;
use graphene_core::uuid::ManipulatorGroupId;
#[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(Debug, Clone)]
pub struct NetworkMetadata {
/// Cache for the bounding box around all nodes in node graph space.
pub bounding_box_subpath: Option<Subpath<ManipulatorGroupId>>,
/// Transform from node graph space to viewport space.
pub node_graph_to_viewport: DAffine2,
}
@@ -61,7 +61,7 @@ impl SnapConstraint {
}
}
pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {
document.snapping_state.tolerance / document.navigation.zoom
document.snapping_state.tolerance / document.document_ptz.zoom
}
fn compare_points(a: &&SnappedPoint, b: &&SnappedPoint) -> Ordering {
@@ -21,7 +21,7 @@ impl GridSnapper {
let document = snap_data.document;
let mut lines = Vec::new();
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.navigation) else {
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return lines;
};
let origin = document.snapping_state.grid.origin;
@@ -47,7 +47,7 @@ impl GridSnapper {
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.navigation) else {
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {
return lines;
};
let spacing = spacing * spacing_multiplier;