diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index a730f47cad..17cb57dade 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -16,6 +16,7 @@ pub enum NodeGraphMessage { nodes: Vec<(NodeId, NodeTemplate)>, new_ids: HashMap, }, + AddPathNode, AddImport, AddExport, Init, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index d7391227c9..930a96af67 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -16,10 +16,11 @@ use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode}; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2, IVec2}; +use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; @@ -119,6 +120,38 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] }); } + NodeGraphMessage::AddPathNode => { + let selected_nodes = network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + if first_layer.is_some() && has_single_selection && is_compatible { + if let Some(layer) = first_layer { + let node_type = "Path".to_string(); + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface); + let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))); + if !is_modifiable { + responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { + node_type: node_type.clone(), + layer: LayerNodeIdentifier::new_unchecked(layer.to_node()), + }); + responses.add(BroadcastEvent::SelectionChanged); + } + } + } + } NodeGraphMessage::AddImport => { network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path); responses.add(NodeGraphMessage::SendGraph); diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 50fb7b8ec0..d7f1c72d7d 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -19,6 +19,7 @@ pub struct MenuBarMessageHandler { pub spreadsheet_view_open: bool, pub message_logging_verbosity: MessageLoggingVerbosity, pub reset_node_definitions_on_open: bool, + pub single_path_node_compatible_layer_selected: bool, } #[message_handler_data] @@ -45,6 +46,7 @@ impl LayoutHolder for MenuBarMessageHandler { let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names; let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents; let reset_node_definitions_on_open = self.reset_node_definitions_on_open; + let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected; let menu_bar_entries = vec![ MenuBarEntry { @@ -418,9 +420,8 @@ impl LayoutHolder for MenuBarMessageHandler { disabled: no_active_document || !has_selected_layers, children: MenuBarEntryChildren(vec![{ let list = ::list(); - list.into_iter() - .map(|i| i.into_iter()) - .flatten() + list.iter() + .flat_map(|i| i.iter()) .map(move |(operation, info)| MenuBarEntry { label: info.label.to_string(), icon: info.icon.as_ref().map(|i| i.to_string()), @@ -436,6 +437,14 @@ impl LayoutHolder for MenuBarMessageHandler { ..MenuBarEntry::default() }, ], + vec![MenuBarEntry { + label: "Make Path Editable".into(), + icon: Some("NodeShape".into()), + shortcut: None, + action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()), + disabled: !single_path_node_compatible_layer_selected, + ..MenuBarEntry::default() + }], ]), ), MenuBarEntry::new_root( diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index daa81274a3..b18b37d3f6 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -18,10 +18,12 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document_migration::*; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; +use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::text::Font; use std::vec; @@ -78,6 +80,7 @@ impl MessageHandler> for Portfolio self.menu_bar_message_handler.has_selected_nodes = false; self.menu_bar_message_handler.has_selected_layers = false; self.menu_bar_message_handler.has_selection_history = (false, false); + self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false; self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open; self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity; self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open; @@ -95,6 +98,30 @@ impl MessageHandler> 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.single_path_node_compatible_layer_selected = { + let selected_nodes = document.network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(document.metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + let is_modifiable = first_layer.map_or(false, |layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))) + }); + + first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable + } } self.menu_bar_message_handler.process_message(message, responses, ()); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index a8b10afaa9..f5ebc33189 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::{ ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, @@ -18,6 +19,7 @@ use crate::messages::tool::common_functionality::shape_editor::{ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use bezier_rs::{Bezier, BezierHandles, TValue}; +use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::transform::ReferencePoint; use graphene_std::vector::click_target::ClickTargetType; @@ -264,6 +266,14 @@ impl LayoutHolder for PathTool { .selected_index(Some(self.options.path_overlay_mode as u32)) .widget_holder(); + // Works only if a single layer is selected and its type is vectordata + let path_node_button = TextButton::new("Make Path Editable") + .icon(Some("NodeShape".into())) + .tooltip("Make Path Editable") + .on_update(|_| NodeGraphMessage::AddPathNode.into()) + .disabled(!self.tool_data.single_path_node_compatible_layer_selected) + .widget_holder(); + let [_checkbox, _dropdown] = { let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path); [pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()] @@ -294,6 +304,7 @@ impl LayoutHolder for PathTool { unrelated_seperator.clone(), path_overlay_mode_widget, unrelated_seperator.clone(), + path_node_button, // checkbox.clone(), // related_seperator.clone(), // dropdown.clone(), @@ -522,6 +533,7 @@ struct PathToolData { drill_through_cycle_count: usize, hovered_layers: Vec, ghost_outline: Vec<(Vec, DAffine2)>, + single_path_node_compatible_layer_selected: bool, } impl PathToolData { @@ -2383,6 +2395,31 @@ impl Fsm for PathToolFsmState { point_select_state: shape_editor.get_dragging_state(&document.network_interface), colinear, }; + + tool_data.single_path_node_compatible_layer_selected = { + let selected_nodes = document.network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(document.metadata()); + let first_layer = selected_layers.next(); + let second_layer = selected_layers.next(); + let has_single_selection = first_layer.is_some() && second_layer.is_none(); + + let compatible_type = first_layer.and_then(|layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| { + let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]); + Some(format!("type:{}", output_type.nested_type())) + }) + }); + + let is_compatible = compatible_type.as_deref() == Some("type:Instances"); + + let is_modifiable = first_layer.map_or(false, |layer| { + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface); + matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_))) + }); + + first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable + }; tool_data.update_selection_status(shape_editor, document); self }