Merge branch 'master' into merge_point

This commit is contained in:
Adesh Gupta
2025-08-31 09:35:19 +05:30
committed by GitHub
64 changed files with 2466 additions and 2137 deletions
@@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, La
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
use glam::IVec2;
use graph_craft::document::NodeId;
use graphene_std::raster::Image;
use graphene_std::raster::color::Color;
@@ -124,12 +125,19 @@ pub enum FrontendMessage {
document_id: DocumentId,
},
UpdateImportsExports {
imports: Vec<(FrontendGraphOutput, i32, i32)>,
exports: Vec<(FrontendGraphInput, i32, i32)>,
#[serde(rename = "addImport")]
add_import: Option<(i32, i32)>,
#[serde(rename = "addExport")]
add_export: Option<(i32, i32)>,
/// If the primary import is not visible, then it is None.
imports: Vec<Option<FrontendGraphOutput>>,
/// If the primary export is not visible, then it is None.
exports: Vec<Option<FrontendGraphInput>>,
/// The primary import location.
#[serde(rename = "importPosition")]
import_position: IVec2,
/// The primary export location.
#[serde(rename = "exportPosition")]
export_position: IVec2,
/// The document network does not have an add import or export button.
#[serde(rename = "addImportExport")]
add_import_export: bool,
},
UpdateInSelectedNetwork {
#[serde(rename = "inSelectedNetwork")]
@@ -17,7 +17,7 @@ use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate, OutputConnector};
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::portfolio::utility_types::PersistentData;
@@ -2766,7 +2766,7 @@ impl DocumentMessageHandler {
});
}
pub fn update_layers_panel_bottom_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
pub fn update_layers_panel_bottom_bar_widgets(&mut self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
if !layers_panel_open {
return;
}
@@ -2776,6 +2776,7 @@ impl DocumentMessageHandler {
let selected_layer = selected_layers.next();
let has_selection = selected_layer.is_some();
let has_multiple_selection = selected_layers.next().is_some();
for _ in selected_layers {}
let widgets = vec![
PopoverButton::new()
@@ -2789,7 +2790,7 @@ impl DocumentMessageHandler {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path);
let (output_type, _) = self.network_interface.output_type(&OutputConnector::node(node_id, 0), &self.selection_network_path);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
@@ -382,7 +382,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
inputs: vec![
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::node(NodeId(2), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
..Default::default()
@@ -771,6 +770,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::node(NodeId(0), 0),
NodeInput::node(NodeId(1), 0),
NodeInput::node(NodeId(2), 0),
@@ -825,8 +825,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
has_primary_output: false,
output_names: vec!["".to_string(), "Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
@@ -883,7 +882,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
@@ -905,13 +904,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Vec2", "TODO").into()],
output_names: vec!["X".to_string(), "Y".to_string()],
has_primary_output: false,
output_names: vec!["".to_string(), "X".to_string(), "Y".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
@@ -77,7 +77,6 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
})
.collect(),
output_names: vec![output_type.to_string()],
has_primary_output: true,
locked: false,
..Default::default()
},
@@ -18,7 +18,11 @@ pub enum NodeGraphMessage {
},
AddPathNode,
AddImport,
AddPrimaryImport,
AddSecondaryImport,
AddExport,
AddPrimaryExport,
AddSecondaryExport,
Init,
SelectedNodesUpdated,
Copy,
@@ -63,6 +67,12 @@ pub enum NodeGraphMessage {
set_to_exposed: bool,
start_transaction: bool,
},
ExposeEncapsulatingPrimaryInput {
exposed: bool,
},
ExposePrimaryExport {
exposed: bool,
},
InsertNode {
node_id: NodeId,
node_template: NodeTemplate,
@@ -1,4 +1,4 @@
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
use super::{document_node_definitions, node_properties};
use crate::consts::GRID_SIZE;
use crate::messages::input_mapper::utility_types::macros::action_keys;
@@ -127,7 +127,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
}
NodeGraphMessage::AddPathNode => {
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
if let Some(layer) = make_path_editable_is_allowed(network_interface) {
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
responses.add(EventMessage::SelectionChanged);
}
@@ -136,10 +136,44 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::AddPrimaryImport => {
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
responses.add(NodeGraphMessage::AddImport);
} else {
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: true });
}
}
NodeGraphMessage::AddSecondaryImport => {
// If necessary, add a hidden primary import before the secondary import
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
responses.add(NodeGraphMessage::AddImport);
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false });
}
// Add the secondary import
responses.add(NodeGraphMessage::AddImport);
}
NodeGraphMessage::AddExport => {
network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::AddPrimaryExport => {
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
responses.add(NodeGraphMessage::AddExport);
} else {
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: true });
}
}
NodeGraphMessage::AddSecondaryExport => {
// If necessary, add a hidden primary import before the secondary import
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
responses.add(NodeGraphMessage::AddExport);
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false });
}
// Add the secondary export
responses.add(NodeGraphMessage::AddExport);
}
NodeGraphMessage::Init => {
responses.add(BroadcastMessage::SubscribeEvent {
on: EventMessage::SelectionChanged,
@@ -401,6 +435,77 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
}
}
NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed } => {
let Some((node_id, network_path)) = breadcrumb_network_path.split_last() else {
return;
};
let encapsulating_connector = InputConnector::node(*node_id, 0);
if !exposed {
network_interface.disconnect_input(&encapsulating_connector, network_path);
}
let Some(mut input) = network_interface.input_from_connector(&encapsulating_connector, network_path).cloned() else {
return;
};
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
*old_exposed = exposed;
}
network_interface.set_input(&encapsulating_connector, input, network_path);
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {
log::error!("Could not get outward wires in remove_import");
return;
};
let Some(downstream_connections) = outward_wires.get(&OutputConnector::Import(0)).cloned() else {
log::error!("Could not get outward wires for import in remove_import");
return;
};
// Disconnect all connections in the encapsulating network
for downstream_connection in &downstream_connections {
network_interface.disconnect_input(downstream_connection, breadcrumb_network_path);
}
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendWires);
}
NodeGraphMessage::ExposePrimaryExport { exposed } => {
let export_connector: InputConnector = InputConnector::Export(0);
if !exposed {
network_interface.disconnect_input(&export_connector, breadcrumb_network_path);
}
let Some(mut input) = network_interface.input_from_connector(&export_connector, breadcrumb_network_path).cloned() else {
return;
};
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
*old_exposed = exposed;
}
network_interface.set_input(&export_connector, input, breadcrumb_network_path);
// Disconnect all connections in the encapsulating network
if let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() {
let Some(outward_wires) = network_interface.outward_wires(encapsulating_path) else {
log::error!("Could not get outward wires in remove_import");
return;
};
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*encapsulating_node, 0)).cloned() else {
log::error!("Could not get outward wires for import in remove_import");
return;
};
for downstream_connection in &downstream_connections {
network_interface.disconnect_input(downstream_connection, encapsulating_path);
}
}
responses.add(NodeGraphMessage::UpdateImportsExports);
}
NodeGraphMessage::InsertNode { node_id, node_template } => {
network_interface.insert_node(node_id, node_template, selection_network_path);
}
@@ -706,21 +811,21 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return;
};
if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() {
if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddExport);
return;
} else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddImport);
return;
} else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
if remove_import_index == 0 {
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false })
} else {
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
}
return;
} else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
if remove_export_index == 0 {
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false })
} else {
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
}
return;
} else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) {
responses.add(DocumentMessage::StartTransaction);
@@ -756,10 +861,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
// Alt-click sets the clicked node as previewed
if alt_click {
if let Some(clicked_node) = clicked_id {
self.preview_on_mouse_up = Some(clicked_node);
}
if alt_click && let Some(clicked_node) = clicked_id {
self.preview_on_mouse_up = Some(clicked_node);
}
// Begin moving an existing wire
@@ -785,14 +888,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.initial_disconnecting = false;
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
if let Some((output_type, source)) = clicked_output
.node_id()
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
{
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
} else {
self.wire_in_progress_type = FrontendGraphDataType::General;
}
let (output_type, source) = &network_interface.output_type(&clicked_output, breadcrumb_network_path);
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(output_type, source);
self.update_node_graph_hints(responses);
return;
@@ -1023,7 +1120,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
};
(position > point.y).then_some(*index)
})
.unwrap_or(modify_import_export.reorder_imports_exports.output_ports().count()),
.filter(|end_index| *end_index > 0) // An import cannot be reordered to be the primary
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.output_ports().count() + 1),
);
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
} else if self.reordering_export.is_some() {
@@ -1043,7 +1141,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
};
(position > point.y).then_some(*index)
})
.unwrap_or(modify_import_export.reorder_imports_exports.input_ports().count()),
.filter(|end_index| *end_index > 0) // An export cannot be reordered to be the primary
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
);
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
}
@@ -1098,27 +1197,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph);
} else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting {
} else if !self.initial_disconnecting
&& input_connector.is_none()
&& let Some(output_connector) = output_connector
{
// If the add node menu is already open, we don't want to open it again
if self.context_menu.is_some() {
return;
}
// Get the output types from the network interface
let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path);
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
warn!("No network_metadata");
return;
};
// Get the compatible type from the output connector
let compatible_type = output_connector.and_then(|output_connector| {
output_connector.node_id().and_then(|node_id| {
// Get the output types from the network interface
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
match type_source {
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
_ => Some(format!("type:{}", output_type.nested_type())),
}
})
});
let compatible_type = match type_source {
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
_ => Some(format!("type:{}", output_type.nested_type())),
};
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
@@ -1442,11 +1541,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::RemoveExport { export_index: usize } => {
network_interface.remove_export(usize, selection_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
@@ -1821,7 +1922,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let shift = ipp.keyboard.get(Key::Shift as usize);
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
log::error!("Could not get selected nodes in PointerMove");
log::error!("Could not get selected nodes in UpdateBoxSelection");
return;
};
let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>();
@@ -1850,29 +1951,25 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
}
NodeGraphMessage::UpdateImportsExports => {
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
let add_import = network_interface
.frontend_import_export_modify(
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::<Vec<_>>(),
breadcrumb_network_path,
)
.into_iter()
.next();
let add_export = network_interface
.frontend_import_export_modify(
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.input_ports().collect::<Vec<_>>(),
breadcrumb_network_path,
)
.into_iter()
.next();
let imports = network_interface.frontend_imports(breadcrumb_network_path);
let exports = network_interface.frontend_exports(breadcrumb_network_path);
let Some((import_position, export_position)) = network_interface.import_export_position(breadcrumb_network_path) else {
log::error!("Could not get import export positions");
return;
};
// Do not show the add import or add export button in the document network;
let add_import_export = !breadcrumb_network_path.is_empty();
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWires);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
add_import,
add_export,
import_position,
export_position,
add_import_export,
});
}
@@ -1990,11 +2087,6 @@ impl NodeGraphMessageHandler {
return;
};
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
warn!("No network in update_selection_action_buttons");
return;
};
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {
warn!("No selected nodes in update_selection_action_buttons");
return;
@@ -2008,6 +2100,7 @@ impl NodeGraphMessageHandler {
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
let selected_layer = selected_layers.next();
let has_multiple_selection = selected_layers.next().is_some();
for _ in selected_layers {}
let mut widgets = vec![
PopoverButton::new()
@@ -2020,7 +2113,7 @@ impl NodeGraphMessageHandler {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
@@ -2106,6 +2199,11 @@ impl NodeGraphMessageHandler {
let mut selection = selected_nodes.selected_nodes();
let (selection, no_other_selections) = (selection.next(), selection.count() == 0);
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
warn!("No network in update_selection_action_buttons");
return;
};
let previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) {
network.exports.iter().find_map(|export| {
let NodeInput::Node { node_id, .. } = export else { return None };
@@ -2338,7 +2436,7 @@ impl NodeGraphMessageHandler {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
let (output_type, _) = context.network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
@@ -2426,110 +2524,48 @@ impl NodeGraphMessageHandler {
}
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path).cloned() else {
return Vec::new();
};
let mut can_be_layer_lookup = HashSet::new();
let mut position_lookup = HashMap::new();
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
log::error!("Could not get nested network when collecting nodes");
return Vec::new();
};
for node_id in network.nodes.keys().cloned().collect::<Vec<_>>() {
if network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path) {
can_be_layer_lookup.insert(node_id);
}
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) {
position_lookup.insert(node_id, position);
} else {
log::error!("Could not get position for node {node_id}");
}
}
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
log::error!("Could not get nested network when collecting nodes");
return Vec::new();
};
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
log::error!("Could not get network_metadata when collecting nodes");
return Vec::new();
};
let mut nodes = Vec::new();
for (&node_id, node) in &network.nodes {
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
let node_id_path = [breadcrumb_network_path, &[node_id]].concat();
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
let primary_input_connector = InputConnector::node(node_id, 0);
let mut inputs = inputs.into_iter().map(|input| {
input.map(|input| FrontendGraphInput {
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
resolved_type: format!("{:?}", &input.ty),
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
name: input.input_name,
description: input.input_description,
connected_to: input.output_connector,
})
});
let primary_input = inputs.next().flatten();
let exposed_inputs = inputs.flatten().collect();
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
Some(FrontendGraphOutput {
data_type: frontend_data_type,
name: "Output 1".to_string(),
description: String::new(),
resolved_type: format!("{output_type:?}"),
connected_to,
})
let primary_input = if network_interface
.input_from_connector(&primary_input_connector, breadcrumb_network_path)
.is_some_and(|input| input.is_exposed())
{
network_interface.frontend_input_from_connector(&primary_input_connector, breadcrumb_network_path)
} else {
None
};
let exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path))
.filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path))
.collect();
let mut exposed_outputs = Vec::new();
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
continue;
}
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path);
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
log::error!("Could not get node_metadata when getting output for {node_id}");
continue;
};
let output_name = node_metadata
.persistent_metadata
.output_names
.get(output_index)
.cloned()
.filter(|output_name| !output_name.is_empty())
.unwrap_or_else(|| output_type.nested_type().to_string());
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
exposed_outputs.push(FrontendGraphOutput {
data_type,
name: output_name,
description: String::new(),
resolved_type: format!("{output_type:?}"),
connected_to,
});
}
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
log::error!("Could not get nested network when collecting nodes");
return Vec::new();
let exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path))
.filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path))
.collect();
let (primary_output_connected_to_layer, primary_input_connected_to_layer) = if network_interface.is_layer(&node_id, breadcrumb_network_path) {
(
network_interface.primary_output_connected_to_layer(&node_id, breadcrumb_network_path),
network_interface.primary_input_connected_to_layer(&node_id, breadcrumb_network_path),
)
} else {
(false, false)
};
let is_export = network.exports.first().is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
let is_export = network_interface
.input_from_connector(&InputConnector::Export(0), breadcrumb_network_path)
.is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
let is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id);
let Some(position) = position_lookup.get(&node_id).map(|pos| (pos.x, pos.y)) else {
let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else {
log::error!("Could not get position for node: {node_id}");
continue;
};
@@ -2555,19 +2591,20 @@ impl NodeGraphMessageHandler {
is_layer: network_interface
.node_metadata(&node_id, breadcrumb_network_path)
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
can_be_layer: can_be_layer_lookup.contains(&node_id),
can_be_layer: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path),
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
primary_input,
exposed_inputs,
primary_output,
exposed_outputs,
primary_output_connected_to_layer,
primary_input_connected_to_layer,
position,
previewed,
visible: node.visible,
visible,
locked,
errors,
ui_only: false,
});
}
@@ -2717,73 +2754,6 @@ impl NodeGraphMessageHandler {
}
}
#[derive(Default)]
struct InputLookup {
input_name: String,
input_description: String,
ty: Type,
type_source: TypeSource,
valid_types: Vec<Type>,
output_connector: Option<OutputConnector>,
}
type FrontendInputsLookup = HashMap<NodeId, Vec<Option<InputLookup>>>;
/// Create a lookup hashmap that can be used to create the frontend inputs. This is needed because `input_type` requires a mutable `network_interface`.
fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: &mut NodeNetworkInterface) -> FrontendInputsLookup {
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
return Default::default();
};
let mut frontend_inputs_lookup = HashMap::new();
for (node_id, index, output_connector, is_exposed) in network
.nodes
.iter()
.flat_map(|(node_id, node)| {
node.inputs
.iter()
.enumerate()
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
})
.collect::<Vec<_>>()
{
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
let lookup = if !is_exposed {
None
} else {
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
Some(InputLookup {
input_name,
input_description,
output_connector,
..Default::default()
})
};
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
}
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
for (index, value) in value.iter_mut().enumerate() {
// Skip not exposed inputs for efficiency
let Some(value) = value else { continue };
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
value.ty = ty;
value.type_source = type_source;
}
}
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
for (index, value) in value.iter_mut().enumerate() {
// Skip not exposed inputs for efficiency
let Some(value) = value else { continue };
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
value.valid_types = network_interface.valid_input_types(&InputConnector::node(node_id, index), breadcrumb_network_path);
}
}
frontend_inputs_lookup
}
impl Default for NodeGraphMessageHandler {
fn default() -> Self {
Self {
@@ -173,6 +173,8 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false),
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default()),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default()),
// ==========================
// PRIMITIVE COLLECTION TYPES
// ==========================
@@ -926,6 +928,22 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
// Add the color input
match &**tagged_value {
TaggedValue::ColorNotInTable(color) => widgets.push(
color_button
.value(FillChoice::Solid(*color))
.allow_none(false)
.on_update(update_value(|input: &ColorInput| TaggedValue::ColorNotInTable(input.value.as_solid().unwrap()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::OptionalColorNotInTable(color) => widgets.push(
color_button
.value(color.map_or(FillChoice::None, FillChoice::Solid))
.allow_none(true)
.on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColorNotInTable(input.value.as_solid()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::Color(color_table) => widgets.push(
color_button
.value(match color_table.iter().next() {
@@ -965,7 +983,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
.on_commit(commit_value)
.widget_holder(),
),
_ => {}
x => warn!("Colour {x:?}"),
}
LayoutGroup::Row { widgets }
@@ -1,4 +1,5 @@
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource};
use crate::messages::portfolio::document::utility_types::network_interface::TypeSource;
use glam::IVec2;
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Type;
@@ -60,7 +61,8 @@ pub struct FrontendGraphInput {
#[serde(rename = "validTypes")]
pub valid_types: Vec<String>,
#[serde(rename = "connectedTo")]
pub connected_to: Option<OutputConnector>,
/// Either "nothing", "import index {index}", or "{node name} output {output_index}".
pub connected_to: String,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -68,11 +70,13 @@ pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub name: String,
pub description: String,
#[serde(rename = "resolvedType")]
pub resolved_type: String,
pub description: String,
/// If connected to an export, it is "export index {index}".
/// If connected to a node, it is "{node name} input {input_index}".
#[serde(rename = "connectedTo")]
pub connected_to: Vec<InputConnector>,
pub connected_to: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -93,13 +97,15 @@ pub struct FrontendNode {
pub primary_output: Option<FrontendGraphOutput>,
#[serde(rename = "exposedOutputs")]
pub exposed_outputs: Vec<FrontendGraphOutput>,
pub position: (i32, i32),
#[serde(rename = "primaryOutputConnectedToLayer")]
pub primary_output_connected_to_layer: bool,
#[serde(rename = "primaryInputConnectedToLayer")]
pub primary_input_connected_to_layer: bool,
pub position: IVec2,
pub visible: bool,
pub locked: bool,
pub previewed: bool,
pub errors: Option<String>,
#[serde(rename = "uiOnly")]
pub ui_only: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, InputPersistentMetadata, NodeNetworkMetadata, NodeTypePersistentMetadata};
use serde_json::Value;
use std::collections::HashMap;
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DocumentNodePersistentMetadataInputNames {
pub reference: Option<String>,
#[serde(default)]
pub display_name: String,
pub input_names: Vec<String>,
pub output_names: Vec<String>,
pub has_primary_output: bool,
#[serde(default)]
pub locked: bool,
#[serde(default)]
pub pinned: bool,
pub node_type_metadata: NodeTypePersistentMetadata,
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadata {
fn from(old: DocumentNodePersistentMetadataInputNames) -> Self {
DocumentNodePersistentMetadata {
input_metadata: Vec::new(),
..old.into()
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DocumentNodePersistentMetadataPropertiesRow {
pub reference: Option<String>,
#[serde(default)]
pub display_name: String,
pub input_properties: Vec<PropertiesRow>,
pub output_names: Vec<String>,
pub has_primary_output: bool,
#[serde(default)]
pub locked: bool,
#[serde(default)]
pub pinned: bool,
pub node_type_metadata: NodeTypePersistentMetadata,
pub network_metadata: Option<NodeNetworkMetadata>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PropertiesRow {
pub input_data: HashMap<String, Value>,
pub widget_override: Option<String>,
#[serde(skip)]
pub input_name: String,
#[serde(skip)]
pub input_description: String,
}
impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadata {
fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self {
let mut input_metadata = Vec::new();
for properties_row in old.input_properties {
input_metadata.push(InputMetadata {
persistent_metadata: InputPersistentMetadata {
input_data: properties_row.input_data,
widget_override: properties_row.widget_override,
input_name: properties_row.input_name,
input_description: properties_row.input_description,
},
..Default::default()
})
}
DocumentNodePersistentMetadata {
reference: old.reference,
display_name: old.display_name,
input_metadata: Vec::new(),
output_names: old.output_names,
locked: old.locked,
pinned: old.pinned,
node_type_metadata: old.node_type_metadata,
network_metadata: old.network_metadata,
}
}
}
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DocumentNodePersistentMetadataHasPrimaryOutput {
pub reference: Option<String>,
#[serde(default)]
pub display_name: String,
pub input_metadata: Vec<InputMetadata>,
pub output_names: Vec<String>,
pub has_primary_output: bool,
#[serde(default)]
pub locked: bool,
#[serde(default)]
pub pinned: bool,
pub node_type_metadata: NodeTypePersistentMetadata,
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadata {
fn from(old: DocumentNodePersistentMetadataHasPrimaryOutput) -> Self {
DocumentNodePersistentMetadata {
reference: old.reference,
display_name: old.display_name,
input_metadata: old.input_metadata,
output_names: old.output_names,
locked: old.locked,
pinned: old.pinned,
node_type_metadata: old.node_type_metadata,
network_metadata: old.network_metadata,
}
}
}
pub fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let value = Value::deserialize(deserializer)?;
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataHasPrimaryOutput>(value.clone()) {
return Ok(document.into());
};
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadata>(value.clone()) {
return Ok(document);
};
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataPropertiesRow>(value.clone()) {
return Ok(document.into());
};
match serde_json::from_value::<DocumentNodePersistentMetadataInputNames>(value.clone()) {
Ok(document) => Ok(document.into()),
Err(e) => Err(serde::de::Error::custom(e)),
}
}
@@ -230,6 +230,14 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
"graphene_math_nodes::CoordinateValueNode",
],
},
NodeReplacement {
node: graphene_std::vector::cut_segments::IDENTIFIER,
aliases: &["graphene_core::vector::SplitSegmentsNode"],
},
NodeReplacement {
node: graphene_std::vector::cut_path::IDENTIFIER,
aliases: &["graphene_core::vector::SplitPathNode"],
},
NodeReplacement {
node: graphene_std::vector::vec_2_to_point::IDENTIFIER,
aliases: &["graphene_core::vector::PositionToPointNode"],
@@ -109,7 +109,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
};
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
}
self.menu_bar_message_handler.process_message(message, responses, ());
@@ -1144,7 +1144,7 @@ impl PortfolioMessageHandler {
}
}
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
fn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
if to_front {
self.document_ids.push_front(document_id);
} else {
@@ -1,8 +1,8 @@
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};
use super::transformation_cage::{BoundingBoxManager, SizeSnapData};
use crate::consts::ROTATE_INCREMENT;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};
@@ -568,25 +568,21 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
(d1 * len1, d2 * len2)
}
pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterface) -> Option<LayerNodeIdentifier> {
// Must have exactly one layer selected
let selected_nodes = network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(metadata);
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
let first_layer = selected_layers.next()?;
if selected_layers.next().is_some() {
return None;
}
for _ in selected_layers {}
// Must be a layer of type Table<Vector>
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
.horizontal_layer_flow()
.nth(1)
.map(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
output_type.nested_type() == concrete!(Table<Vector>).nested_type()
})
.unwrap_or_default();
if !compatible_type {
let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?;
let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
if output_type.nested_type() != concrete!(Table<Vector>).nested_type() {
return None;
}
@@ -4,6 +4,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::compass_rose::Axis;
use crate::messages::tool::common_functionality::measure;
use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::common_functionality::snapping;
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
@@ -12,7 +13,7 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::renderer::Quad;
use graphene_std::renderer::{Quad, Rect};
use graphene_std::table::Table;
#[derive(Default, ExtractField)]
@@ -241,6 +242,39 @@ impl Fsm for ArtboardToolFsmState {
tool_data.bounding_box_manager.take();
}
// Measure with Alt held down between selected artboard and hovered layers/artboards
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
let alt_pressed = input.keyboard.get(Key::Alt as usize);
let quick_measurement_enabled = overlay_context.visibility_settings.quick_measurement();
let not_resizing = !matches!(state, ArtboardToolFsmState::ResizingBounds);
if quick_measurement_enabled && not_resizing && alt_pressed {
// Get the selected artboard bounds
let selected_artboard_bounds = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)).map(Rect::from_box);
// Find hovered artboard or regular layer
let hovered_artboard = ArtboardToolData::hovered_artboard(document, input);
let hovered_layer = document.click_xray(input).find(|&layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
// Get bounds for the hovered object (prioritize artboards)
let hovered_bounds = if let Some(artboard) = hovered_artboard {
document.metadata().bounding_box_document(artboard).map(Rect::from_box)
} else if let Some(layer) = hovered_layer {
document.metadata().bounding_box_document(layer).map(Rect::from_box)
} else {
None
};
// If both selected artboard and hovered object bounds exist, overlay measurement lines
if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_artboard_bounds, hovered_bounds) {
// Don't measure if it's the same artboard
if selected_artboard_bounds != Some(hovered_bounds) {
let document_to_viewport = document.metadata().document_to_viewport;
measure::overlay(selected_bounds, hovered_bounds, document_to_viewport, document_to_viewport, &mut overlay_context);
}
}
}
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
self
@@ -3171,7 +3171,7 @@ impl Fsm for PathToolFsmState {
colinear,
};
tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
tool_data.update_selection_status(shape_editor, document);
tool_data.update_merge_point_toggle(shape_editor, document, tool_action_data.preferences.vector_meshes);