Add shifting of layers in stacks as blocks that collide and bump other layers/nodes (#1940)
* Shift nodes as blocks * Implement rubber banding * Improve upstream locking when shifting layers * WIP: Reworked shifting * WIP: Reworked node shifting * Finish shifting * Fix demo artwork * Code review * Right click to end shift * Improve rubber banding * Fix clippy issues * Skip collision for intersecting nodes * Rubber banding bug fix * Fix ctrl+delete node in chain * Grip drag * Fix layer width * Add icon to frontend for the solo drag grip * Reconnect during ctrl+delete * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
a7840b252d
commit
78337f9b8e
|
|
@ -1,7 +1,9 @@
|
|||
// Graph
|
||||
pub const GRID_SIZE: u32 = 24;
|
||||
pub const EXPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;
|
||||
pub const IMPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;
|
||||
pub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
|
||||
pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;
|
||||
pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
|
||||
pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;
|
||||
|
||||
// Viewport
|
||||
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ mod test {
|
|||
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
|
||||
});
|
||||
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
|
||||
editor.draw_rect(0., 800., 12., 200.);
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::MouseButton;
|
|||
use crate::messages::input_mapper::utility_types::macros::*;
|
||||
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
||||
use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping};
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::Direction;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;
|
||||
|
|
@ -61,11 +62,10 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
|
||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NodeGraphMessage::PointerUp),
|
||||
entry!(KeyUp(Escape); action_dispatch=NodeGraphMessage::CloseCreateNodeMenu),
|
||||
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: false }),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: false }),
|
||||
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: true }),
|
||||
entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: true }),
|
||||
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),
|
||||
entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
|
||||
|
|
@ -75,6 +75,10 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyC); modifiers=[Shift], action_dispatch=NodeGraphMessage::PrintSelectedNodeCoordinates),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=NodeGraphMessage::SendClickTargets),
|
||||
entry!(KeyUp(KeyC); action_dispatch=NodeGraphMessage::EndSendClickTargets),
|
||||
entry!(KeyDown(ArrowUp); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Up, rubber_band: false }),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Right, rubber_band: false }),
|
||||
entry!(KeyDown(ArrowDown); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Down, rubber_band: false }),
|
||||
entry!(KeyDown(ArrowLeft); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Left, rubber_band: false }),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
|
|
@ -301,7 +305,7 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// DocumentMessage
|
||||
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
||||
entry!(KeyUp(Escape); action_dispatch=DocumentMessage::GraphViewOverlay { open: false }),
|
||||
entry!(KeyUp(Escape); action_dispatch=DocumentMessage::Escape),
|
||||
entry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||
entry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||
entry!(KeyDown(KeyP); modifiers=[Alt], action_dispatch=DocumentMessage::DebugPrintDocument),
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ pub enum DocumentMessage {
|
|||
EnterNestedNetwork {
|
||||
node_id: NodeId,
|
||||
},
|
||||
Escape,
|
||||
ExitNestedNetwork {
|
||||
steps_back: usize,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
info!("{:#?}", self.network_interface);
|
||||
}
|
||||
DocumentMessage::DeleteSelectedLayers => {
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
|
||||
}
|
||||
DocumentMessage::DeselectAllLayers => {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
|
|
@ -400,6 +400,26 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||
}
|
||||
DocumentMessage::Escape => {
|
||||
if self.node_graph_handler.drag_start.is_some() {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
self.node_graph_handler.drag_start = None;
|
||||
} else if self
|
||||
.node_graph_handler
|
||||
.context_menu
|
||||
.as_ref()
|
||||
.is_some_and(|context_menu| matches!(context_menu.context_menu_data, super::node_graph::utility_types::ContextMenuData::CreateNode))
|
||||
{
|
||||
// Close the context menu
|
||||
self.node_graph_handler.context_menu = None;
|
||||
responses.add(FrontendMessage::UpdateContextMenuInformation { context_menu_information: None });
|
||||
self.node_graph_handler.wire_in_progress_from_connector = None;
|
||||
self.node_graph_handler.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
} else {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: false });
|
||||
}
|
||||
}
|
||||
DocumentMessage::ExitNestedNetwork { steps_back } => {
|
||||
for _ in 0..steps_back {
|
||||
self.breadcrumb_network_path.pop();
|
||||
|
|
@ -434,10 +454,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(FrontendMessage::TriggerGraphViewOverlay { open });
|
||||
// Update the tilt menu bar buttons to be disabled when the graph is open
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
if open {
|
||||
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });
|
||||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlayToggle => {
|
||||
|
|
@ -639,16 +661,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
resize_opposite_corner,
|
||||
} => {
|
||||
self.backup(responses);
|
||||
if self.graph_view_overlay_open {
|
||||
responses.add(NodeGraphMessage::ShiftNodes {
|
||||
node_ids: self.network_interface.selected_nodes(&[]).unwrap().selected_nodes().cloned().collect(),
|
||||
displacement_x: if delta_x == 0.0 { 0 } else { delta_x.signum() as i32 },
|
||||
displacement_y: if delta_y == 0.0 { 0 } else { delta_y.signum() as i32 },
|
||||
move_upstream: ipp.keyboard.get(Key::Shift as usize),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
||||
let delta = DVec2::new(delta_x, delta_y);
|
||||
|
|
@ -862,7 +874,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
self.selected_layers_reorder(relative_index_offset, responses);
|
||||
}
|
||||
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![];
|
||||
|
||||
|
|
@ -965,7 +977,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
}
|
||||
DocumentMessage::StartTransaction => self.backup(responses),
|
||||
DocumentMessage::ToggleLayerExpansion { id } => {
|
||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
|
||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
||||
if self.collapsed.0.contains(&layer) {
|
||||
self.collapsed.0.retain(|&collapsed_layer| collapsed_layer != layer);
|
||||
} else {
|
||||
|
|
@ -1040,7 +1052,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
// Delete empty group folder
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![layer.to_node()],
|
||||
reconnect: true,
|
||||
delete_children: true,
|
||||
});
|
||||
}
|
||||
DocumentMessage::PTZUpdate => {
|
||||
|
|
@ -1123,23 +1135,26 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
|
||||
// Additional actions if there are any selected layers
|
||||
if self.network_interface.selected_nodes(&[]).unwrap().selected_layers(self.metadata()).next().is_some() {
|
||||
let select = actions!(DocumentMessageDiscriminant;
|
||||
let mut select = actions!(DocumentMessageDiscriminant;
|
||||
DeleteSelectedLayers,
|
||||
DuplicateSelectedLayers,
|
||||
GroupSelectedLayers,
|
||||
NudgeSelectedLayers,
|
||||
SelectedLayersLower,
|
||||
SelectedLayersLowerToBack,
|
||||
SelectedLayersRaise,
|
||||
SelectedLayersRaiseToFront,
|
||||
UngroupSelectedLayers,
|
||||
);
|
||||
if !self.graph_view_overlay_open {
|
||||
select.extend(actions!(DocumentMessageDiscriminant; NudgeSelectedLayers));
|
||||
}
|
||||
common.extend(select);
|
||||
}
|
||||
|
||||
// Additional actions if the node graph is open
|
||||
if self.graph_view_overlay_open {
|
||||
common.extend(actions!(DocumentMessageDiscriminant;
|
||||
GraphViewOverlay,
|
||||
Escape
|
||||
));
|
||||
common.extend(self.node_graph_handler.actions_additional_if_node_graph_is_open());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||
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, &[]);
|
||||
|
|
@ -202,7 +202,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||
for artboard in network_interface.all_artboards() {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![artboard.to_node()],
|
||||
reconnect: false,
|
||||
delete_children: false,
|
||||
});
|
||||
}
|
||||
// TODO: Replace deleted artboards with merge nodes
|
||||
|
|
|
|||
|
|
@ -116,7 +116,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
|
||||
|
|
@ -130,7 +130,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::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||
|
|
@ -222,7 +222,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
};
|
||||
let export_node = 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use super::utility_types::Direction;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
|
|
@ -18,7 +19,6 @@ pub enum NodeGraphMessage {
|
|||
Init,
|
||||
SelectedNodesUpdated,
|
||||
Copy,
|
||||
CloseCreateNodeMenu,
|
||||
CreateNodeFromContextMenu {
|
||||
node_id: Option<NodeId>,
|
||||
node_type: String,
|
||||
|
|
@ -32,10 +32,10 @@ pub enum NodeGraphMessage {
|
|||
Cut,
|
||||
DeleteNodes {
|
||||
node_ids: Vec<NodeId>,
|
||||
reconnect: bool,
|
||||
delete_children: bool,
|
||||
},
|
||||
DeleteSelectedNodes {
|
||||
reconnect: bool,
|
||||
delete_children: bool,
|
||||
},
|
||||
DisconnectInput {
|
||||
input_connector: InputConnector,
|
||||
|
|
@ -111,15 +111,18 @@ pub enum NodeGraphMessage {
|
|||
node_id: NodeId,
|
||||
alias: String,
|
||||
},
|
||||
ShiftNodePosition {
|
||||
node_id: NodeId,
|
||||
x: i32,
|
||||
y: i32,
|
||||
},
|
||||
SetToNodeOrLayer {
|
||||
node_id: NodeId,
|
||||
is_layer: bool,
|
||||
},
|
||||
ShiftNodes {
|
||||
node_ids: Vec<NodeId>,
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
move_upstream: bool,
|
||||
ShiftSelectedNodes {
|
||||
direction: Direction,
|
||||
rubber_band: bool,
|
||||
},
|
||||
TogglePreview {
|
||||
node_id: NodeId,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::application::generate_uuid;
|
|||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, FrontendGraphDataType};
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, NodeTemplate, OutputConnector, Previewing};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||
|
|
@ -17,6 +17,7 @@ use graphene_core::*;
|
|||
use renderer::{ClickTarget, Quad};
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeGraphHandlerData<'a> {
|
||||
|
|
@ -36,21 +37,23 @@ pub struct NodeGraphMessageHandler {
|
|||
pub node_graph_errors: GraphErrors,
|
||||
has_selection: bool,
|
||||
widgets: [LayoutGroup; 2],
|
||||
drag_start: Option<DragStart>,
|
||||
pub drag_start: Option<DragStart>,
|
||||
/// Used to add a transaction for the first node move when dragging.
|
||||
begin_dragging: bool,
|
||||
/// Stored in node graph coordinates
|
||||
box_selection_start: Option<DVec2>,
|
||||
/// If the grip icon is held during a drag, then shift without pushing other nodes
|
||||
shift_without_push: bool,
|
||||
disconnecting: Option<InputConnector>,
|
||||
initial_disconnecting: bool,
|
||||
/// Node to select on pointer up if multiple nodes are selected and they were not dragged.
|
||||
select_if_not_dragged: Option<NodeId>,
|
||||
/// The start of the dragged line that cannot be moved, stored in node graph coordinates
|
||||
wire_in_progress_from_connector: Option<DVec2>,
|
||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||
/// The end point of the dragged line that can be moved, stored in node graph coordinates
|
||||
wire_in_progress_to_connector: Option<DVec2>,
|
||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||
/// State for the context menu popups.
|
||||
context_menu: Option<ContextMenuInformation>,
|
||||
pub context_menu: Option<ContextMenuInformation>,
|
||||
/// Index of selected node to be deselected on pointer up when shift clicking an already selected node
|
||||
pub deselect_on_pointer_up: Option<usize>,
|
||||
/// Adds the auto panning functionality to the node graph when dragging a node or selection box to the edge of the viewport.
|
||||
|
|
@ -126,15 +129,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
||||
}
|
||||
NodeGraphMessage::CloseCreateNodeMenu => {
|
||||
self.context_menu = None;
|
||||
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
||||
context_menu_information: self.context_menu.clone(),
|
||||
});
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
}
|
||||
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, x, y } => {
|
||||
let node_id = node_id.unwrap_or_else(|| NodeId(generate_uuid()));
|
||||
|
||||
|
|
@ -154,12 +148,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
node_id,
|
||||
node_template: node_template.clone(),
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftNodes {
|
||||
node_ids: vec![node_id],
|
||||
displacement_x: x,
|
||||
displacement_y: y,
|
||||
move_upstream: false,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftNodePosition { node_id, x, y });
|
||||
// Only auto connect to the dragged wire if the node is being added to the currently opened network
|
||||
if let Some(output_connector_position) = self.wire_in_progress_from_connector {
|
||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||
|
|
@ -203,16 +192,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
NodeGraphMessage::Cut => {
|
||||
responses.add(NodeGraphMessage::Copy);
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
|
||||
}
|
||||
NodeGraphMessage::DeleteNodes { node_ids, reconnect } => {
|
||||
network_interface.delete_nodes(node_ids, reconnect, selection_network_path);
|
||||
NodeGraphMessage::DeleteNodes { node_ids, delete_children } => {
|
||||
network_interface.delete_nodes(node_ids, delete_children, selection_network_path);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
// Deletes selected_nodes. If `reconnect` is true, then all children nodes (secondary input) of the selected nodes are deleted and the siblings (primary input/output) are reconnected.
|
||||
// If `reconnect` is false, then only the selected nodes are deleted and not reconnected.
|
||||
NodeGraphMessage::DeleteSelectedNodes { reconnect } => {
|
||||
NodeGraphMessage::DeleteSelectedNodes { delete_children } => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else {
|
||||
log::error!("Could not get selected nodes in DeleteSelectedNodes");
|
||||
return;
|
||||
|
|
@ -220,7 +209,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
|
||||
reconnect,
|
||||
delete_children,
|
||||
})
|
||||
}
|
||||
NodeGraphMessage::DisconnectInput { input_connector } => {
|
||||
|
|
@ -232,15 +221,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
NodeGraphMessage::DuplicateSelectedNodes => {
|
||||
let all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let copy_ids = all_selected_nodes.iter().enumerate().map(|(new, id)| (*id, NodeId(new as u64))).collect::<HashMap<NodeId, NodeId>>();
|
||||
|
||||
// Copy the selected nodes
|
||||
let nodes = network_interface.copy_nodes(©_ids, selection_network_path).collect::<Vec<_>>();
|
||||
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect::<HashMap<_, _>>();
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids: new_ids.clone() });
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: new_ids.values().cloned().collect(),
|
||||
});
|
||||
}
|
||||
NodeGraphMessage::EnterNestedNetwork => {
|
||||
let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, selection_network_path) else {
|
||||
|
|
@ -348,6 +339,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
}
|
||||
|
||||
if network_interface.grip_from_click(click, selection_network_path).is_some() {
|
||||
self.shift_without_push = true;
|
||||
}
|
||||
|
||||
let clicked_id = network_interface.node_from_click(click, selection_network_path);
|
||||
let clicked_input = network_interface.input_connector_from_click(click, selection_network_path);
|
||||
let clicked_output = network_interface.output_connector_from_click(click, selection_network_path);
|
||||
|
|
@ -355,6 +350,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
// Create the add node popup on right click, then exit
|
||||
if right_click {
|
||||
if self.drag_start.is_some() {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
self.drag_start = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let context_menu_data = if let Some(node_id) = clicked_id {
|
||||
ContextMenuData::ToggleLayer {
|
||||
node_id,
|
||||
|
|
@ -613,24 +614,57 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
|
||||
}
|
||||
} else if let Some(drag_start) = &mut self.drag_start {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else {
|
||||
log::error!("Could not get selected nodes in PointerMove");
|
||||
return;
|
||||
};
|
||||
if self.begin_dragging {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.begin_dragging = false;
|
||||
}
|
||||
let graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32);
|
||||
if drag_start.round_x != graph_delta.x || drag_start.round_y != graph_delta.y {
|
||||
responses.add(NodeGraphMessage::ShiftNodes {
|
||||
node_ids: selected_nodes.selected_nodes().cloned().collect(),
|
||||
displacement_x: graph_delta.x - drag_start.round_x,
|
||||
displacement_y: graph_delta.y - drag_start.round_y,
|
||||
move_upstream: ipp.keyboard.get(shift as usize),
|
||||
});
|
||||
drag_start.round_x = graph_delta.x;
|
||||
drag_start.round_y = graph_delta.y;
|
||||
|
||||
let mut graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32);
|
||||
let previous_round_x = drag_start.round_x;
|
||||
let previous_round_y = drag_start.round_y;
|
||||
|
||||
drag_start.round_x = graph_delta.x;
|
||||
drag_start.round_y = graph_delta.y;
|
||||
|
||||
graph_delta.x -= previous_round_x;
|
||||
graph_delta.y -= previous_round_y;
|
||||
|
||||
while graph_delta != IVec2::ZERO {
|
||||
match graph_delta.x.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Right,
|
||||
rubber_band: true,
|
||||
});
|
||||
graph_delta.x -= 1;
|
||||
}
|
||||
Ordering::Less => {
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Left,
|
||||
rubber_band: true,
|
||||
});
|
||||
graph_delta.x += 1;
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
|
||||
match graph_delta.y.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Down,
|
||||
rubber_band: true,
|
||||
});
|
||||
graph_delta.y -= 1;
|
||||
}
|
||||
Ordering::Less => {
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Up,
|
||||
rubber_band: true,
|
||||
});
|
||||
graph_delta.y += 1;
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
} else if self.box_selection_start.is_some() {
|
||||
responses.add(NodeGraphMessage::UpdateBoxSelection);
|
||||
|
|
@ -649,6 +683,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
warn!("No network_metadata");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(node_to_deselect) = self.deselect_on_pointer_up {
|
||||
let mut new_selected_nodes = selected_nodes.selected_nodes_ref().clone();
|
||||
new_selected_nodes.remove(node_to_deselect);
|
||||
|
|
@ -703,7 +738,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
});
|
||||
return;
|
||||
}
|
||||
} else if let Some(drag_start) = &self.drag_start {
|
||||
}
|
||||
// End of dragging a node
|
||||
else if let Some(drag_start) = &self.drag_start {
|
||||
self.shift_without_push = false;
|
||||
// Reset all offsets to end the rubber banding while dragging
|
||||
network_interface.unload_stack_dependents_y_offset(selection_network_path);
|
||||
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else { return };
|
||||
// Only select clicked node if multiple are selected and they were not dragged
|
||||
if let Some(select_if_not_dragged) = self.select_if_not_dragged {
|
||||
if drag_start.start_x == point.x
|
||||
|
|
@ -718,6 +759,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
|
||||
// Try expand the upstream chain for all layers if there is an eligible node
|
||||
let Some(network) = network_interface.network(selection_network_path) else { return };
|
||||
for layer in network
|
||||
.nodes
|
||||
.keys()
|
||||
.filter(|node_id| network_interface.is_layer(node_id, selection_network_path))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
network_interface.try_set_upstream_to_chain(&InputConnector::node(layer, 1), selection_network_path);
|
||||
}
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
|
||||
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else { return };
|
||||
// Check if a single node was dragged onto a wire and that the node was dragged onto the wire
|
||||
if selected_nodes.selected_nodes_ref().len() == 1 && !self.begin_dragging {
|
||||
let selected_node_id = selected_nodes.selected_nodes_ref()[0];
|
||||
|
|
@ -732,7 +787,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
|
||||
// Check that neither the primary input or output of the selected node are already connected.
|
||||
if !has_primary_output_connection && primary_input_is_value {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(selected_node_id, selection_network_path) else {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||
return;
|
||||
};
|
||||
|
|
@ -857,8 +912,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
}
|
||||
self.select_if_not_dragged = None
|
||||
self.select_if_not_dragged = None;
|
||||
}
|
||||
|
||||
self.drag_start = None;
|
||||
self.begin_dragging = false;
|
||||
self.box_selection_start = None;
|
||||
|
|
@ -991,13 +1047,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
NodeGraphMessage::SetInput { input_connector, input } => {
|
||||
network_interface.set_input(&input_connector, input, selection_network_path);
|
||||
}
|
||||
NodeGraphMessage::ShiftNodes {
|
||||
node_ids,
|
||||
displacement_x,
|
||||
displacement_y,
|
||||
move_upstream,
|
||||
} => {
|
||||
network_interface.shift_selected_nodes(node_ids, displacement_x, displacement_y, move_upstream, selection_network_path);
|
||||
NodeGraphMessage::ShiftSelectedNodes { direction, rubber_band } => {
|
||||
network_interface.shift_selected_nodes(direction, self.shift_without_push, selection_network_path);
|
||||
|
||||
if !rubber_band {
|
||||
network_interface.unload_stack_dependents_y_offset(selection_network_path);
|
||||
}
|
||||
|
||||
if graph_view_overlay_open {
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
|
|
@ -1022,6 +1077,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
||||
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
||||
}
|
||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||
return;
|
||||
|
|
@ -1253,16 +1311,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = vec![];
|
||||
|
||||
if self
|
||||
.context_menu
|
||||
.as_ref()
|
||||
.is_some_and(|context_menu| matches!(context_menu.context_menu_data, ContextMenuData::CreateNode))
|
||||
{
|
||||
common.extend(actions!(NodeGraphMessageDiscriminant; CloseCreateNodeMenu));
|
||||
}
|
||||
|
||||
let common = vec![];
|
||||
common
|
||||
}
|
||||
}
|
||||
|
|
@ -1282,6 +1331,7 @@ impl NodeGraphMessageHandler {
|
|||
ToggleSelectedLocked,
|
||||
ToggleSelectedVisibility,
|
||||
PrintSelectedNodeCoordinates,
|
||||
ShiftSelectedNodes,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -1732,7 +1782,7 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
let mut selected_parents = 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 && !selected_layers.contains(&ancestor.to_node()) {
|
||||
selected_parents.insert(ancestor.to_node());
|
||||
}
|
||||
|
|
@ -1741,7 +1791,7 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
for (&node_id, node_metadata) in &network_interface.network_metadata(&[]).unwrap().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
|
||||
|
|
@ -1883,6 +1933,7 @@ impl Default for NodeGraphMessageHandler {
|
|||
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }],
|
||||
drag_start: None,
|
||||
begin_dragging: false,
|
||||
shift_without_push: false,
|
||||
box_selection_start: None,
|
||||
disconnecting: None,
|
||||
initial_disconnecting: false,
|
||||
|
|
|
|||
|
|
@ -174,10 +174,18 @@ pub struct FrontendClickTargets {
|
|||
pub layer_click_targets: Vec<String>,
|
||||
#[serde(rename = "portClickTargets")]
|
||||
pub port_click_targets: Vec<String>,
|
||||
#[serde(rename = "visibilityClickTargets")]
|
||||
pub visibility_click_targets: Vec<String>,
|
||||
#[serde(rename = "iconClickTargets")]
|
||||
pub icon_click_targets: Vec<String>,
|
||||
#[serde(rename = "allNodesBoundingBox")]
|
||||
pub all_nodes_bounding_box: String,
|
||||
#[serde(rename = "importExportsBoundingBox")]
|
||||
pub import_exports_bounding_box: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,13 +226,13 @@ impl LayerNodeIdentifier {
|
|||
Self(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })
|
||||
}
|
||||
|
||||
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node in the document network
|
||||
/// 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) -> Self {
|
||||
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self {
|
||||
debug_assert!(
|
||||
network_interface.is_layer(&node_id, &Vec::new()),
|
||||
network_interface.is_layer(&node_id, network_path),
|
||||
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
|
||||
network_interface.network(&[]).unwrap().nodes.get(&node_id)
|
||||
network_interface.network(network_path).unwrap().nodes.get(&node_id)
|
||||
);
|
||||
Self::new_unchecked(node_id)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -552,7 +552,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
continue;
|
||||
};
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && document.network_interface.is_absolute(&layer.to_node(), &[]) {
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) =
|
||||
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
|
||||
use graph_craft::document::{generate_uuid, NodeId};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ImaginateTool {
|
||||
fsm_state: ImaginateToolFsmState,
|
||||
|
|
@ -101,7 +98,7 @@ impl Fsm for ImaginateToolFsmState {
|
|||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||
shape_data.start(document, input);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
|
||||
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
// // Utility function to offset the position of each consecutive node
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ impl SelectToolData {
|
|||
for layer in document.network_interface.shallowest_unique_layers(&[]) {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![layer.to_node()],
|
||||
reconnect: true,
|
||||
delete_children: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1203,6 +1203,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
|
|||
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(LayerNodeIdentifier::new(
|
||||
document.network_interface.root_node(&[]).expect("Root node should exist when dragging layers").node_id,
|
||||
&document.network_interface,
|
||||
&[],
|
||||
))]);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: tool_data
|
||||
|
|
|
|||
|
|
@ -162,15 +162,40 @@
|
|||
transparent calc(6px * sqrt(2) / 2)
|
||||
);
|
||||
|
||||
// Arrow triangle (#eee fill)
|
||||
// Array of 2x3 dots (fill: --color-e-nearwhite)
|
||||
--icon-drag-grip: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 24" fill="%23eee">\
|
||||
<circle cx="0.5" cy="1.5" r="0.5" /><circle cx="3.5" cy="1.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="4.5" r="0.5" /><circle cx="3.5" cy="4.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="7.5" r="0.5" /><circle cx="3.5" cy="7.5" r="0.5" />\
|
||||
</svg>\
|
||||
');
|
||||
// Array of 2x3 dots (fill: --color-f-white)
|
||||
--icon-drag-grip-hover: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 24" fill="%23fff">\
|
||||
<circle cx="0.5" cy="1.5" r="0.5" /><circle cx="3.5" cy="1.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="4.5" r="0.5" /><circle cx="3.5" cy="4.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="7.5" r="0.5" /><circle cx="3.5" cy="7.5" r="0.5" />\
|
||||
</svg>\
|
||||
');
|
||||
// Array of 2x3 dots (fill: --color-8-uppergray)
|
||||
--icon-drag-grip-disabled: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 24" fill="%23888">\
|
||||
<circle cx="0.5" cy="1.5" r="0.5" /><circle cx="3.5" cy="1.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="4.5" r="0.5" /><circle cx="3.5" cy="4.5" r="0.5" />\
|
||||
<circle cx="0.5" cy="7.5" r="0.5" /><circle cx="3.5" cy="7.5" r="0.5" />\
|
||||
</svg>\
|
||||
');
|
||||
|
||||
// Arrow triangle (fill: --color-e-nearwhite)
|
||||
--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23eee" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>\
|
||||
');
|
||||
// Arrow triangle (#fff fill)
|
||||
// Arrow triangle (fill: --color-f-white)
|
||||
--icon-expand-collapse-arrow-hover: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23fff" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>\
|
||||
');
|
||||
// Arrow triangle (#888 fill)
|
||||
// Arrow triangle (fill: --color-8-uppergray)
|
||||
--icon-expand-collapse-arrow-disabled: url('data:image/svg+xml;utf8,\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23888" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>\
|
||||
');
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@
|
|||
{#each $nodeGraph.clickTargets.portClickTargets as pathString}
|
||||
<path class="port" d={pathString} />
|
||||
{/each}
|
||||
{#each $nodeGraph.clickTargets.visibilityClickTargets as pathString}
|
||||
{#each $nodeGraph.clickTargets.iconClickTargets as pathString}
|
||||
<path class="visibility" d={pathString} />
|
||||
{/each}
|
||||
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
|
||||
|
|
@ -642,6 +642,7 @@
|
|||
{node.displayName}
|
||||
</span>
|
||||
</div>
|
||||
<div class="solo-drag-grip" title="Drag only this layer without pushing others outside the stack"></div>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
data-visibility-button
|
||||
|
|
@ -1238,6 +1239,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
.solo-drag-grip {
|
||||
width: 8px;
|
||||
height: 24px;
|
||||
background-position: 2px 8px;
|
||||
right: calc(-12px + 24px);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.solo-drag-grip:hover,
|
||||
&.selected .solo-drag-grip {
|
||||
background-image: var(--icon-drag-grip);
|
||||
|
||||
&:hover {
|
||||
background-image: var(--icon-drag-grip-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.visibility {
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
|
|
@ -1247,6 +1265,7 @@
|
|||
left: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);
|
||||
}
|
||||
|
||||
.solo-drag-grip,
|
||||
.visibility,
|
||||
.input.ports,
|
||||
.input.ports .port {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ export type FrontendClickTargets = {
|
|||
readonly nodeClickTargets: string[];
|
||||
readonly layerClickTargets: string[];
|
||||
readonly portClickTargets: string[];
|
||||
readonly visibilityClickTargets: string[];
|
||||
readonly iconClickTargets: string[];
|
||||
readonly allNodesBoundingBox: string;
|
||||
readonly importExportsBoundingBox: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -622,7 +622,10 @@ impl EditorHandle {
|
|||
self.dispatch(message);
|
||||
|
||||
let id = NodeId(id);
|
||||
let message = NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true };
|
||||
let message = NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![id],
|
||||
delete_children: true,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
|
@ -779,7 +782,7 @@ impl EditorHandle {
|
|||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let layer = LayerNodeIdentifier::new(node, &document.network_interface);
|
||||
let layer = LayerNodeIdentifier::new(node, &document.network_interface, &[]);
|
||||
if layer.has_children(document.metadata()) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue