mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 11:28:30 +08:00
Merge branch 'master' into grid_shape
This commit is contained in:
+1
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user