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
parent 6fd5b76430
commit 84ac2e274e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 332 additions and 495 deletions

View File

@ -100,6 +100,7 @@ pub enum DocumentMessage {
},
RenderRulers,
RenderScrollbars,
ResetTransform,
SaveDocument,
SelectAllLayers,
SelectedLayersLower,

View File

@ -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(),

View File

@ -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);

View File

@ -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)

View File

@ -81,6 +81,9 @@ pub enum NodeGraphMessage {
shift: Key,
},
PointerUp,
PointerOutsideViewport {
shift: Key,
},
PrintSelectedNodeCoordinates,
RunDocumentGraph,
SelectedNodesAdd {

View File

@ -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(),
}
}
}

View File

@ -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>,
}

View File

@ -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;

View File

@ -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;

View File

@ -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,
}

View File

@ -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 {

View File

@ -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;

View File

@ -507,7 +507,7 @@
size={24}
icon={node.visible ? "EyeVisible" : "EyeHidden"}
action={() => {
/*Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown*/
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
}}
tooltip={node.visible ? "Visible" : "Hidden"}
/>
@ -658,7 +658,8 @@
</div>
</div>
<!-- Box select widget -->
<!-- Box selection widget -->
<!-- TODO: Make its initial corner stay put (in graph space) when panning around -->
{#if $nodeGraph.box}
<div
class="box-selection"

View File

@ -5,7 +5,7 @@ use dyn_any::{DynAny, StaticType};
pub use graphene_core::uuid::generate_uuid;
use graphene_core::{ProtoNodeIdentifier, Type};
use glam::{DAffine2, IVec2};
use glam::IVec2;
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
@ -665,9 +665,6 @@ pub struct NodeNetwork {
pub imports_metadata: (NodeId, IVec2),
#[serde(default = "default_export_metadata")]
pub exports_metadata: (NodeId, IVec2),
/// Transform from node graph space to viewport space.
#[serde(default)]
pub node_graph_to_viewport: DAffine2,
}
impl std::hash::Hash for NodeNetwork {
@ -690,7 +687,6 @@ impl Default for NodeNetwork {
previewing: Default::default(),
imports_metadata: default_import_metadata(),
exports_metadata: default_export_metadata(),
node_graph_to_viewport: DAffine2::default(),
}
}
}

View File

@ -133,6 +133,7 @@ impl BorrowTree {
/// Pushes new nodes into the tree and return orphaned nodes
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<Vec<NodeId>, GraphErrors> {
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();
// TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type.
for (id, node) in proto_network.nodes {
if !self.nodes.contains_key(&id) {
self.push_node(id, node, typing_context).await?;