Merge branch 'master' into grid_shape

This commit is contained in:
0SlowPoke0
2025-07-24 03:31:35 +05:30
committed by GitHub
96 changed files with 4672 additions and 4234 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
name = "graphite-editor"
publish = false
version = "0.0.0"
rust-version = "1.85"
rust-version = "1.88"
authors = ["Graphite Authors <contact@graphite.rs>"]
edition = "2024"
readme = "../README.md"
@@ -19,5 +19,6 @@ pub enum InputMapperMessage {
// Messages
PointerMove,
PointerShake,
WheelScroll,
}
@@ -54,14 +54,15 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
//
// NodeGraphMessage
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }),
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }),
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }),
entry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode),
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
@@ -417,7 +418,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
entry!(KeyDown(KeyW); modifiers=[Accel,Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument),
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
@@ -440,7 +441,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
];
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings;
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));
// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`
@@ -457,6 +458,8 @@ pub fn input_mappings() -> Mapping {
sort(&mut wheel_scroll);
// Sort `pointer_move`
sort(&mut pointer_move);
// Sort `pointer_shake`
sort(&mut pointer_shake);
Mapping {
key_up,
@@ -466,6 +469,7 @@ pub fn input_mappings() -> Mapping {
double_click,
wheel_scroll,
pointer_move,
pointer_shake,
}
}
@@ -90,6 +90,7 @@ macro_rules! mapping {
let mut double_click = KeyMappingEntries::mouse_buttons_arrays();
let mut wheel_scroll = KeyMappingEntries::new();
let mut pointer_move = KeyMappingEntries::new();
let mut pointer_shake = KeyMappingEntries::new();
$(
// Each of the many entry slices, one specified per action
@@ -104,6 +105,7 @@ macro_rules! mapping {
InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],
InputMapperMessage::WheelScroll => &mut wheel_scroll,
InputMapperMessage::PointerMove => &mut pointer_move,
InputMapperMessage::PointerShake => &mut pointer_shake,
};
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
corresponding_list.push(entry.clone());
@@ -111,7 +113,7 @@ macro_rules! mapping {
}
)*
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move)
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake)
}};
}
@@ -14,6 +14,7 @@ pub struct Mapping {
pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],
pub wheel_scroll: KeyMappingEntries,
pub pointer_move: KeyMappingEntries,
pub pointer_shake: KeyMappingEntries,
}
impl Default for Mapping {
@@ -47,6 +48,7 @@ impl Mapping {
InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],
InputMapperMessage::WheelScroll => &self.wheel_scroll,
InputMapperMessage::PointerMove => &self.pointer_move,
InputMapperMessage::PointerShake => &self.pointer_shake,
}
}
@@ -59,6 +61,7 @@ impl Mapping {
InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize],
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
InputMapperMessage::PointerMove => &mut self.pointer_move,
InputMapperMessage::PointerShake => &mut self.pointer_shake,
}
}
}
@@ -12,6 +12,7 @@ pub enum InputPreprocessorMessage {
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
CurrentTime { timestamp: u64 },
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
}
@@ -97,6 +97,14 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
self.translate_mouse_event(mouse_state, false, responses);
}
InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
self.mouse.position = mouse_state.position;
responses.add(InputMapperMessage::PointerShake);
}
InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
self.time = timestamp;
@@ -332,6 +332,9 @@ pub enum NumberInputMode {
pub struct NodeCatalog {
pub disabled: bool,
#[serde(rename = "initialSearchTerm")]
pub intial_search: String,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
@@ -182,8 +182,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
device_pixel_ratio,
} = context;
let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path);
let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport();
match message {
// Sub-messages
DocumentMessage::Navigation(message) => {
@@ -191,11 +189,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
network_interface: &mut self.network_interface,
breadcrumb_network_path: &self.breadcrumb_network_path,
ipp,
selection_bounds: if self.graph_view_overlay_open {
selected_nodes_bounding_box_viewport
} else {
selected_visible_layers_bounding_box_viewport
},
document_ptz: &mut self.document_ptz,
graph_view_overlay_open: self.graph_view_overlay_open,
preferences,
@@ -259,7 +252,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
AlignAxis::X => DVec2::X,
AlignAxis::Y => DVec2::Y,
};
let Some(combined_box) = self.selected_visible_layers_bounding_box_viewport() else {
let Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else {
return;
};
@@ -486,7 +479,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
FlipAxis::X => DVec2::new(-1., 1.),
FlipAxis::Y => DVec2::new(1., -1.),
};
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
let center = (max + min) / 2.;
let bbox_trans = DAffine2::from_translation(-center);
let mut added_transaction = false;
@@ -506,7 +499,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}
DocumentMessage::RotateSelectedLayers { degrees } => {
// Get the bounding box of selected layers in viewport space
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
// Calculate the center of the bounding box to use as rotation pivot
let center = (max + min) / 2.;
// Transform that moves pivot point to origin
@@ -1063,13 +1056,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.selected_layers_reorder(relative_index_offset, responses);
}
DocumentMessage::ClipLayer { id } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
responses.add(DocumentMessage::AddTransaction);
responses.add(GraphOperationMessage::ClipModeToggle { layer });
}
DocumentMessage::SelectLayer { id, ctrl, shift } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
let mut nodes = vec![];
@@ -1266,7 +1259,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(OverlaysMessage::Draw);
}
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
let metadata = self.metadata();
let is_collapsed = self.collapsed.0.contains(&layer);
@@ -1323,7 +1316,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.network_interface.document_network().nodes.contains_key(node_id))
.filter_map(|(node_id, click_targets)| {
self.network_interface.is_layer(&node_id, &[]).then(|| {
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
(layer, click_targets)
})
})
@@ -1708,31 +1701,6 @@ impl DocumentMessageHandler {
.last()
}
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_layers(&self.network_interface)
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn selected_visible_and_unlock_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&self.network_interface)
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&self.network_interface)
.map(|layer| self.metadata().nonzero_bounding_box(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn document_network(&self) -> &NodeNetwork {
self.network_interface.document_network()
}
@@ -2741,7 +2709,22 @@ impl DocumentMessageHandler {
.tooltip("Add an operation to the end of this layer's chain of nodes")
.disabled(!has_selection || has_multiple_selection)
.popover_layout({
let node_chooser = NodeCatalog::new()
// Showing only compatible types
let compatible_type = selected_layer.and_then(|layer| {
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);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
});
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
if let Some(layer) = selected_layer {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
@@ -119,7 +119,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
if let NodeInput::Node { node_id, .. } = &primary_input {
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface, &[]), artboard_layer, 0, &[]);
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]);
} else {
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
@@ -124,7 +124,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template();
self.network_interface.insert_node(new_id, new_merge_node, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
LayerNodeIdentifier::new(new_id, self.network_interface)
}
/// Creates an artboard as the primary export for the document network
@@ -138,7 +138,7 @@ impl<'a> ModifyInputsContext<'a> {
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
]);
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
LayerNodeIdentifier::new(new_id, self.network_interface)
}
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
@@ -236,7 +236,7 @@ impl<'a> ModifyInputsContext<'a> {
self.layer_node.or_else(|| {
let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?;
if self.network_interface.is_layer(&export_node, &[]) {
Some(LayerNodeIdentifier::new(export_node, self.network_interface, &[]))
Some(LayerNodeIdentifier::new(export_node, self.network_interface))
} else {
None
}
@@ -18,7 +18,6 @@ pub struct NavigationMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub breadcrumb_network_path: &'a [NodeId],
pub ipp: &'a InputPreprocessorMessageHandler,
pub selection_bounds: Option<[DVec2; 2]>,
pub document_ptz: &'a mut PTZ,
pub graph_view_overlay_open: bool,
pub preferences: &'a PreferencesMessageHandler,
@@ -39,7 +38,6 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
network_interface,
breadcrumb_network_path,
ipp,
selection_bounds,
document_ptz,
graph_view_overlay_open,
preferences,
@@ -386,9 +384,16 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
// Fully zooms in on the selected
NavigationMessage::FitViewportToSelection => {
let selection_bounds = if graph_view_overlay_open {
network_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path)
} else {
network_interface.selected_layers_artwork_bounding_box_viewport()
};
if let Some(bounds) = selection_bounds {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in FitViewportToSelection");
return;
};
@@ -16,6 +16,7 @@ pub enum NodeGraphMessage {
nodes: Vec<(NodeId, NodeTemplate)>,
new_ids: HashMap<NodeId, NodeId>,
},
AddPathNode,
AddImport,
AddExport,
Init,
@@ -81,6 +82,9 @@ pub enum NodeGraphMessage {
node_id: NodeId,
parent: LayerNodeIdentifier,
},
SetChainPosition {
node_id: NodeId,
},
PasteNodes {
serialized_nodes: String,
},
@@ -97,6 +101,7 @@ pub enum NodeGraphMessage {
PointerOutsideViewport {
shift: Key,
},
ShakeNode,
RemoveImport {
import_index: usize,
},
@@ -10,20 +10,24 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::network_interface::{
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
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 bezier_rs::Subpath;
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;
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
use graphene_std::*;
use kurbo::{Line, Point};
use renderer::Quad;
use std::cmp::Ordering;
@@ -55,6 +59,8 @@ pub struct NodeGraphMessageHandler {
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
/// plus a flag indicating if it has been dragged since the mousedown began.
pub drag_start: Option<(DragStart, bool)>,
// Store the selected chain nodes on drag start so they can be reconnected if shaken
pub drag_start_chain_nodes: Vec<NodeId>,
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
/// plus a flag indicating if it has been dragged since the mousedown began.
box_selection_start: Option<(DVec2, bool)>,
@@ -119,6 +125,38 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<VectorData>");
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);
@@ -568,6 +606,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
}
NodeGraphMessage::SetChainPosition { node_id } => {
network_interface.set_chain_position(&node_id, selection_network_path);
}
NodeGraphMessage::PasteNodes { serialized_nodes } => {
let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
Ok(d) => d,
@@ -821,6 +862,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
};
self.drag_start = Some((drag_start, false));
let selected_chain_nodes = updated_selected
.iter()
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
.copied()
.collect::<Vec<_>>();
self.drag_start_chain_nodes = selected_chain_nodes
.iter()
.flat_map(|selected| {
network_interface
.upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow)
.skip(1)
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
})
.collect::<Vec<_>>();
self.begin_dragging = true;
self.node_has_moved_in_drag = false;
self.update_node_graph_hints(responses);
@@ -1188,10 +1243,39 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
{
return None;
}
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
let ps = [p1, p2, p3, p4];
let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);
let wire = subpath_to_kurbo_bezpath(wire);
let intersect = wire.segments().any(|segment| {
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));
!segment.intersect_line(top_line).is_empty()
|| !segment.intersect_line(bottom_line).is_empty()
|| !segment.intersect_line(left_line).is_empty()
|| !segment.intersect_line(right_line).is_empty()
});
(intersect || inside).then_some((input, is_stack))
})
.collect::<Vec<_>>();
// Prioritize vertical thick lines and cancel if there are multiple potential wires
let mut node_wires = Vec::new();
let mut stack_wires = Vec::new();
@@ -1270,6 +1354,135 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.auto_panning.stop(&messages, responses);
}
}
NodeGraphMessage::ShakeNode => {
let Some(drag_start) = &self.drag_start else {
log::error!("Drag start should be initialized when shaking a node");
return;
};
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
return;
};
let viewport_location = ipp.mouse.position;
let point = network_metadata
.persistent_metadata
.navigation_metadata
.node_graph_to_viewport
.inverse()
.transform_point2(viewport_location);
// Collect the distance to move the shaken nodes after the undo
let graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32);
// Undo to the state of the graph before shaking
responses.add(DocumentMessage::AbortTransaction);
// Add a history step to abort to the state before shaking if right clicked
responses.add(DocumentMessage::StartTransaction);
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
log::error!("Could not get selected nodes in ShakeNode");
return;
};
let mut all_selected_nodes = selected_nodes.0.iter().copied().collect::<HashSet<_>>();
for selected_layer in selected_nodes
.0
.iter()
.filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path))
.copied()
.collect::<Vec<_>>()
{
for sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) {
all_selected_nodes.insert(sole_dependent);
}
}
for selected_node in &all_selected_nodes {
// Handle inputs of selected node
for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) {
let input_connector = InputConnector::node(*selected_node, input_index);
// Only disconnect inputs to non selected nodes
if network_interface
.upstream_output_connector(&input_connector, selection_network_path)
.and_then(|connector| connector.node_id())
.is_some_and(|node_id| !all_selected_nodes.contains(&node_id))
{
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
}
}
let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path);
let first_deselected_upstream_node = network_interface
.upstream_flow_back_from_nodes(vec![*selected_node], selection_network_path, FlowType::PrimaryFlow)
.find(|upstream_node| !all_selected_nodes.contains(upstream_node));
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
log::error!("Could not get output wires in shake input");
continue;
};
// Disconnect output wires to non selected nodes
for output_index in 0..number_of_outputs {
let output_connector = OutputConnector::node(*selected_node, output_index);
if let Some(downstream_connections) = outward_wires.get(&output_connector) {
for &input_connector in downstream_connections {
if input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) {
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
}
}
}
}
// Handle reconnection
// Find first non selected upstream node by primary flow
if let Some(first_deselected_upstream_node) = first_deselected_upstream_node {
let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else {
log::error!("Could not get downstream_connections_to_first_output in shake node");
return;
};
// Reconnect only if all downstream outputs are not selected
if !downstream_connections_to_first_output
.iter()
.any(|connector| connector.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id)))
{
// Find what output on the deselected upstream node to reconnect to
for output_index in 0..network_interface.number_of_outputs(&first_deselected_upstream_node, selection_network_path) {
let output_connector = &OutputConnector::node(first_deselected_upstream_node, output_index);
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
log::error!("Could not get output wires in shake input");
continue;
};
if let Some(inputs) = outward_wires.get(output_connector) {
// This can only run once
if inputs.iter().any(|input_connector| {
input_connector
.node_id()
.is_some_and(|upstream_node| all_selected_nodes.contains(&upstream_node) && input_connector.input_index() == 0)
}) {
// Output index is the output of the deselected upstream node to reconnect to
for downstream_connections_to_first_output in &downstream_connections_to_first_output {
responses.add(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::node(first_deselected_upstream_node, output_index),
input_connector: *downstream_connections_to_first_output,
});
}
}
}
// Set all chain nodes back to chain position
// TODO: Fix
// for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) {
// responses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset });
// }
}
}
}
}
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
@@ -1354,6 +1567,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
nodes.push(*node_id);
}
for error in &self.node_graph_errors {
if error.node_path.contains(node_id) {
nodes.push(*node_id);
}
}
}
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
@@ -1785,6 +2003,12 @@ impl NodeGraphMessageHandler {
));
}
if self.drag_start.is_some() {
common.extend(actions!(NodeGraphMessageDiscriminant;
ShakeNode,
));
}
common
}
@@ -1824,26 +2048,57 @@ impl NodeGraphMessageHandler {
let selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0;
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
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();
let mut widgets = vec![
PopoverButton::new()
.icon(Some("Node".to_string()))
.tooltip("New Node (Right Click)")
.popover_layout({
let node_chooser = NodeCatalog::new()
.on_update(move |node_type| {
let node_id = NodeId::new();
// Showing only compatible types
let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) {
(true, false, Some(layer)) => {
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, &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
}
_ => None,
};
Message::Batched {
messages: Box::new([
NodeGraphMessage::CreateNodeFromContextMenu {
node_id: Some(node_id),
node_type: node_type.clone(),
xy: None,
add_transaction: true,
}
.into(),
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
]),
let single_layer_selected = selection_includes_layers && !has_multiple_selection;
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
if let (true, Some(layer)) = (single_layer_selected, selected_layer) {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
}
.into()
} else {
let node_id = NodeId::new();
Message::Batched {
messages: Box::new([
NodeGraphMessage::CreateNodeFromContextMenu {
node_id: Some(node_id),
node_type: node_type.clone(),
xy: None,
add_transaction: true,
}
.into(),
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
]),
}
}
})
.widget_holder();
@@ -2115,7 +2370,22 @@ impl NodeGraphMessageHandler {
.icon(Some("Node".to_string()))
.tooltip("Add an operation to the end of this layer's chain of nodes")
.popover_layout({
let node_chooser = NodeCatalog::new()
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
let compatible_type = {
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, &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
};
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
@@ -2366,19 +2636,19 @@ impl NodeGraphMessageHandler {
let mut ancestors_of_selected = HashSet::new();
let mut descendants_of_selected = HashSet::new();
for selected_layer in &selected_layers {
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).ancestors(network_interface.document_metadata()) {
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) {
if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer {
ancestors_of_selected.insert(ancestor.to_node());
}
}
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).descendants(network_interface.document_metadata()) {
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) {
descendants_of_selected.insert(descendant.to_node());
}
}
for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata {
if node_metadata.persistent_metadata.is_layer() {
let layer = LayerNodeIdentifier::new(node_id, network_interface, &[]);
let layer = LayerNodeIdentifier::new(node_id, network_interface);
let children_allowed =
// The layer has other layers as children along the secondary input's horizontal flow
@@ -2559,6 +2829,7 @@ impl Default for NodeGraphMessageHandler {
node_has_moved_in_drag: false,
shift_without_push: false,
box_selection_start: None,
drag_start_chain_nodes: Vec::new(),
selection_before_pointer_down: Vec::new(),
disconnecting: None,
initial_disconnecting: false,
@@ -250,12 +250,8 @@ impl LayerNodeIdentifier {
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks.
#[track_caller]
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self {
debug_assert!(
network_interface.is_layer(&node_id, network_path),
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
network_interface.nested_network(network_path).unwrap().nodes.get(&node_id)
);
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {
debug_assert!(network_interface.is_layer(&node_id, &[]), "Layer identifier constructed from non-layer node {node_id}",);
Self::new_unchecked(node_id)
}
@@ -203,12 +203,12 @@ impl NodeNetworkInterface {
}
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
pub fn downstream_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<LayerNodeIdentifier> {
pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeId> {
let mut id = *node_id;
while !self.is_layer(&id, network_path) {
id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?;
}
Some(LayerNodeIdentifier::new(id, self, network_path))
Some(id)
}
/// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself.
@@ -388,8 +388,8 @@ impl NodeNetworkInterface {
}
// If a chain node does not have a selected downstream layer, then set the position to absolute
let downstream_layer = self.downstream_layer(node_id, network_path);
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) {
let downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path);
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) {
let Some(position) = self.position(node_id, network_path) else {
log::error!("Could not get position in create_node_template");
return None;
@@ -1244,7 +1244,7 @@ impl NodeNetworkInterface {
.as_ref()
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
{
Some(LayerNodeIdentifier::new(*node_id, self, &[]))
Some(LayerNodeIdentifier::new(*node_id, self))
} else {
None
}
@@ -3025,7 +3025,7 @@ impl NodeNetworkInterface {
// Helper functions for mutable getters
impl NodeNetworkInterface {
pub fn upstream_chain_nodes(&mut self, network_path: &[NodeId]) -> Vec<NodeId> {
pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
log::error!("Could not get selected nodes in upstream_chain_nodes");
return Vec::new();
@@ -3156,7 +3156,7 @@ impl NodeNetworkInterface {
self.document_metadata.document_to_viewport = transform;
}
pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node) = self.document_node(node_id, network_path) else {
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
return false;
@@ -3362,6 +3362,24 @@ impl NodeNetworkInterface {
.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])
}
pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(&node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(&node, &[]) && !self.is_layer(&node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space
pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
@@ -3451,7 +3469,7 @@ impl NodeNetworkInterface {
let Some(first_root_layer) = self
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self, &[])) } else { None })
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None })
else {
return;
};
@@ -3467,7 +3485,7 @@ impl NodeNetworkInterface {
if horizontal_root_node_id == first_root_layer.to_node() {
for current_node_id in horizontal_flow_iter {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
if current_node_id == first_root_layer.to_node() {
awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));
@@ -3484,7 +3502,7 @@ impl NodeNetworkInterface {
// Skip the horizontal_root_node_id node
for current_node_id in horizontal_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
children.push((parent_layer_node, current_layer_node));
@@ -3505,7 +3523,7 @@ impl NodeNetworkInterface {
for current_node_id in primary_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
// Create a new layer for the top of each stack, and add it as a child to the previous parent
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
children.push(current_layer_node);
@@ -3568,7 +3586,7 @@ impl NodeNetworkInterface {
}
stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| {
let mut current_path = path.clone();
let mut current_path: Vec<NodeId> = path.clone();
current_path.push(*node_id);
current_path
}));
@@ -5085,12 +5103,45 @@ impl NodeNetworkInterface {
else {
log::error!("Could not set chain position for layer node {node_id}");
}
// let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id());
// let Some(previous_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
// log::error!("Could not get previous_upstream_node_position");
// return;
// };
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
// Reload click target of the layer which encapsulate the chain
if let Some(downstream_layer) = self.downstream_layer(node_id, network_path) {
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) {
self.unload_node_click_targets(&downstream_layer, network_path);
}
self.unload_all_nodes_bounding_box(network_path);
// let Some(new_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
// log::error!("Could not get new_upstream_node_position");
// return;
// };
// if let Some(previous_upstream_node) = {
// let x_delta = new_upstream_node_position.x - previous_upstream_node_position.x;
// // Upstream node got shifted to left, so shift all upstream absolute sole dependents
// if x_delta != 0 {
// let upstream_absolute_nodes = SelectedNodes(
// self.upstream_flow_back_from_nodes(vec![previous_upstream_node], network_path, FlowType::UpstreamFlow)
// .into_iter()
// .filter(|node_id| self.is_absolute(node_id, network_path))
// .collect::<Vec<_>>(),
// );
// let old_selected_nodes = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), upstream_absolute_nodes);
// if x_delta < 0 {
// for _ in 0..x_delta.abs() {
// self.shift_selected_nodes(Direction::Left, false, network_path);
// }
// } else {
// for _ in 0..x_delta.abs() {
// self.shift_selected_nodes(Direction::Right, false, network_path);
// }
// }
// let _ = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), old_selected_nodes);
// }
// }
}
fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<NodeId> {
@@ -5205,7 +5256,7 @@ impl NodeNetworkInterface {
/// node_id is the first chain node, not the layer
fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else {
let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else {
log::error!("Could not get downstream layer in set_upstream_chain_to_absolute");
return;
};
@@ -5218,7 +5269,7 @@ impl NodeNetworkInterface {
if self.is_chain(upstream_id, network_path) {
self.set_absolute_position(upstream_id, previous_position, network_path);
// Reload click target of the layer which used to encapsulate the chain
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
self.unload_node_click_targets(&downstream_layer, network_path);
}
// If there is an upstream layer then stop breaking the chain
else {
@@ -5297,8 +5348,8 @@ impl NodeNetworkInterface {
// Deselect chain nodes upstream from a selected layer
if self.is_chain(selected_node, network_path)
&& self
.downstream_layer(selected_node, network_path)
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node()))
.downstream_layer_for_chain_node(selected_node, network_path)
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer))
{
node_ids.remove(selected_node);
}
@@ -5947,31 +5998,6 @@ impl NodeNetworkInterface {
self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);
self.set_chain_position(node_id, network_path);
} else {
// TODO: Implement a more robust horizontal shift system when inserting a node into a chain.
// This should be done by breaking the chain and shifting the sole dependents for each node upstream of the insertion.
// Before inserting the node, shift the layer right 7 units so that all sole dependents are also shifted
// let input_connector = InputConnector::node(parent.to_node(), 0);
// let old_upstream = self.upstream_output_connector(&input_connector, network_path);
// This also needs to disconnect from the downstream layer
// self.disconnect_input(&input_connector, network_path);
// let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
// log::error!("Could not get selected nodes in move_layer_to_stack");
// return;
// };
// let old_selected_nodes = selected_nodes.replace_with(vec![parent.to_node()]);
// for _ in 0..7 {
// self.shift_selected_nodes(Direction::Left, false, network_path);
// }
// // Grip drag it back to the right
// for _ in 0..7 {
// self.shift_selected_nodes(Direction::Right, true, network_path);
// }
// let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
// if let Some(old_upstream) = old_upstream {
// self.create_wire(&old_upstream, &input_connector, network_path);
// }
// Insert the node in the gap and set the upstream to a chain
self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);
self.force_set_upstream_to_chain(node_id, network_path);
@@ -6778,13 +6804,6 @@ impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersisten
}
}
#[derive(serde::Serialize, serde::Deserialize)]
enum NodePersistentMetadataVersions {
DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow),
NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames),
NodePersistentMetadata(DocumentNodePersistentMetadata),
}
fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
where
D: serde::Deserializer<'de>,
@@ -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 = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::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(
@@ -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<PortfolioMessage, PortfolioMessageContext<'_>> 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<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.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<VectorData>");
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, ());
@@ -762,6 +789,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
if node_graph_open {
responses.add(NodeGraphMessage::UpdateGraphBarRight);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires)
} else {
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -1000,7 +1000,7 @@ impl ShapeState {
} else {
// Push both in and out handles into the correct position
for ((handle, sign), other_anchor) in handles.iter().zip([1., -1.]).zip(&anchor_positions) {
let Some(anchor_vector) = other_anchor.map(|position| (position - anchor_position)) else {
let Some(anchor_vector) = other_anchor.map(|position| position - anchor_position) else {
continue;
};
@@ -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<LayerNodeIdentifier>,
ghost_outline: Vec<(Vec<ClickTargetType>, 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<VectorData>");
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
}
@@ -1323,7 +1323,7 @@ mod test_transform_layer {
let document = editor.active_document_mut();
let group_children = document.network_interface.downstream_layers(&group_layer.to_node(), &[]);
if !group_children.is_empty() {
Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface, &[]))
Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface))
} else {
None
}