From 2bd213f1aa5cc56538c6287c29d968ce35af31ff Mon Sep 17 00:00:00 2001 From: adamgerhant <116332429+adamgerhant@users.noreply.github.com> Date: Wed, 14 Aug 2024 03:27:42 -0700 Subject: [PATCH] Improve layer panel positioning for upstream nodes (#1928) * Improve layer panel positioning for upstream nodes * highlight parents * Final improvements * Fill for selection box, bug fixes * Code review --------- Co-authored-by: Keavon Chambers --- editor/src/consts.rs | 1 + .../document/document_message_handler.rs | 9 + .../node_graph/node_graph_message_handler.rs | 127 +++---- .../document/overlays/grid_overlays.rs | 12 +- .../document/overlays/utility_functions.rs | 8 +- .../document/overlays/utility_types.rs | 14 +- .../utility_types/network_interface.rs | 313 +++++++++++------- .../portfolio/document/utility_types/nodes.rs | 2 + .../tool/common_functionality/measure.rs | 4 +- .../tool/common_functionality/snapping.rs | 14 +- .../transformation_cage.rs | 2 +- .../tool/tool_messages/gradient_tool.rs | 2 +- .../messages/tool/tool_messages/path_tool.rs | 7 +- .../messages/tool/tool_messages/pen_tool.rs | 6 +- .../tool/tool_messages/select_tool.rs | 6 +- .../messages/tool/tool_messages/text_tool.rs | 4 +- frontend/src/components/Editor.svelte | 10 + frontend/src/components/panels/Layers.svelte | 8 +- frontend/src/components/views/Graph.svelte | 23 +- frontend/src/wasm-communication/messages.ts | 2 + .../src/dynamic_executor.rs | 2 +- 21 files changed, 321 insertions(+), 255 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 26e3f6a5..1117b717 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -70,6 +70,7 @@ pub const ASYMPTOTIC_EFFECT: f64 = 0.5; pub const SCALE_EFFECT: f64 = 0.5; // Colors +// Keep changes to these colors updated with `Editor.svelte` pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; pub const COLOR_OVERLAY_WHITE: &str = "#ffffff"; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 4e926c9a..f7a9a403 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -648,6 +648,15 @@ impl MessageHandler> 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: delta_x.signum() as i32, + displacement_y: 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); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index cb0bab3a..e0bb6f49 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -809,6 +809,11 @@ impl<'a> MessageHandler> for NodeGrap } } + // Auto convert node to layer when inserting on a single stack wire + if stack_wires.len() == 1 && node_wires.is_empty() { + network_interface.set_to_node_or_layer(&selected_node_id, selection_network_path, true) + } + let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) { if stack_wires.len() == 1 { stack_wires.first() @@ -987,71 +992,12 @@ impl<'a> MessageHandler> for NodeGrap network_interface.set_input(&input_connector, input, selection_network_path); } NodeGraphMessage::ShiftNodes { - mut node_ids, + node_ids, displacement_x, displacement_y, move_upstream, } => { - if move_upstream { - for node_id in network_interface.upstream_flow_back_from_nodes(node_ids.clone(), selection_network_path, network_interface::FlowType::UpstreamFlow) { - if network_interface.is_absolute(&node_id, selection_network_path) && node_ids.iter().all(|id| *id != node_id) { - node_ids.push(node_id); - } - } - } - - let mut filtered_node_ids = Vec::new(); - for selected_node in &node_ids { - // Deselect chain nodes upstream from a selected layer - if network_interface.is_chain(selected_node, selection_network_path) - && network_interface - .downstream_layer(selected_node, selection_network_path) - .is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node())) - { - // Deselect stack nodes upstream from a selected layer - continue; - } - - // Deselect stack nodes upstream from a selected layer - let mut is_upstream_from_selected_absolute_layer = false; - let mut current_node = *selected_node; - loop { - if network_interface.is_absolute(selected_node, selection_network_path) { - break; - } - let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else { - break; - }; - let Some(outward_wires) = outward_wires.get(&OutputConnector::node(current_node, 0)) else { - break; - }; - if outward_wires.is_empty() { - break; - } - let Some(downstream_node) = outward_wires[0].node_id() else { - break; - }; - if outward_wires[0].input_index() != 0 { - break; - } - if !network_interface.is_layer(&downstream_node, selection_network_path) { - break; - } - // Break the iteration if the layer is absolute(top of stack), or it is selected - if network_interface.is_absolute(&downstream_node, selection_network_path) || node_ids.contains(&downstream_node) { - is_upstream_from_selected_absolute_layer = node_ids.contains(&downstream_node); - break; - } - current_node = downstream_node; - } - if !is_upstream_from_selected_absolute_layer { - filtered_node_ids.push(*selected_node) - } - } - - for node_id in filtered_node_ids { - network_interface.shift_node(&node_id, IVec2::new(displacement_x, displacement_y), selection_network_path); - } + network_interface.shift_selected_nodes(node_ids, displacement_x, displacement_y, move_upstream, selection_network_path); if graph_view_overlay_open { responses.add(NodeGraphMessage::SendGraph); @@ -1225,8 +1171,12 @@ impl<'a> MessageHandler> for NodeGrap log::error!("Could not get selected nodes in PointerMove"); return; }; - let previous_selection = selected_nodes.selected_nodes_ref().clone(); - let mut nodes = if shift { previous_selection.clone() } else { Vec::new() }; + let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::>(); + let mut nodes = if shift { + selected_nodes.selected_nodes_ref().iter().cloned().collect::>() + } else { + HashSet::new() + }; let all_nodes = network_metadata.persistent_metadata.node_metadata.keys().cloned().collect::>(); for node_id in all_nodes { let Some(click_targets) = network_interface.node_click_targets(&node_id, selection_network_path) else { @@ -1237,11 +1187,13 @@ impl<'a> MessageHandler> for NodeGrap .node_click_target .intersect_rectangle(Quad::from_box([box_selection_start, box_selection_end_graph]), DAffine2::IDENTITY) { - nodes.push(node_id); + nodes.insert(node_id); } } if nodes != previous_selection { - responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + responses.add(NodeGraphMessage::SelectedNodesSet { + nodes: nodes.into_iter().collect::>(), + }); } responses.add(FrontendMessage::UpdateBox { box_selection }) } @@ -1777,10 +1729,35 @@ impl NodeGraphMessageHandler { .selected_layers(network_interface.document_metadata()) .map(|layer| layer.to_node()) .collect::>(); + + 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()) { + if ancestor != LayerNodeIdentifier::ROOT_PARENT && !selected_layers.contains(&ancestor.to_node()) { + selected_parents.insert(ancestor.to_node()); + } + } + } + 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 children_allowed = + // The layer has other layers as children along the secondary input's horizontal flow + layer.has_children(network_interface.document_metadata()) + || ( + // Check if the last node in the chain has an exposed left input + network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id| + network_interface.network(&[]).unwrap().nodes.get(&node_id).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| { + if network_interface.is_layer(&node_id, &[]) { + node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some()) + } else { + node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some()) + } + })) + ); + let parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| { if layer != LayerNodeIdentifier::ROOT_PARENT { network_interface.network(&[]).unwrap().nodes.get(&layer.to_node()).map(|node| node.visible).unwrap_or_default() @@ -1797,30 +1774,26 @@ impl NodeGraphMessageHandler { } }); + let is_selected_parent = selected_parents.contains(&node_id); + let data = LayerPanelEntry { id: node_id, - children_allowed: - // The layer has other layers as children along the secondary input's horizontal flow - layer.has_children(network_interface.document_metadata()) - || ( - // At least one secondary input is exposed on this layer node - network_interface.network(&[]).unwrap().nodes.get(&node_id).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node_id| node_id.inputs.iter().skip(1).any(|input| input.is_exposed())) && - // But nothing is connected to it, since we only get 1 item (ourself) when we ask for the flow from the secondary input - network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).count() == 1 - ), + children_allowed, children_present: layer.has_children(network_interface.document_metadata()), expanded: layer.has_children(network_interface.document_metadata()) && !collapsed.0.contains(&layer), depth: layer.ancestors(network_interface.document_metadata()).count() - 1, - parent_id: layer.parent(network_interface.document_metadata()).and_then(|parent| if parent != LayerNodeIdentifier::ROOT_PARENT { Some(parent.to_node()) } else { None }), - //reference: network_interface.get_reference(&node_id), + parent_id: layer + .parent(network_interface.document_metadata()) + .and_then(|parent| if parent != LayerNodeIdentifier::ROOT_PARENT { Some(parent.to_node()) } else { None }), alias: network_interface.frontend_display_name(&node_id, &[]), tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() }, visible: network_interface.is_visible(&node_id, &[]), parents_visible, unlocked: !network_interface.is_locked(&node_id, &[]), parents_unlocked, - selected: selected_layers.contains(&node_id), + selected: selected_layers.contains(&node_id) || is_selected_parent, in_selected_network: selection_network_path.is_empty(), + selected_parent: is_selected_parent, }; responses.add(FrontendMessage::UpdateDocumentLayerDetails { data }); } diff --git a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs index 1d194369..f89e4808 100644 --- a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs +++ b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs @@ -38,10 +38,10 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: } else { DVec2::new(secondary_pos, primary_end) }; - overlay_context.colored_line( + overlay_context.line( document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), - &("#".to_string() + &grid_color.rgba_hex()), + Some(&("#".to_string() + &grid_color.rgba_hex())), ); } } @@ -114,10 +114,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x; let start = DVec2::new(x_pos, min_y); let end = DVec2::new(x_pos, max_y); - overlay_context.colored_line( + overlay_context.line( document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), - &("#".to_string() + &grid_color.rgba_hex()), + Some(&("#".to_string() + &grid_color.rgba_hex())), ); } @@ -132,10 +132,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y; let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos))); let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos))); - overlay_context.colored_line( + overlay_context.line( document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), - &("#".to_string() + &grid_color.rgba_hex()), + Some(&("#".to_string() + &grid_color.rgba_hex())), ); } } diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index c9d39373..e8945850 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -39,17 +39,17 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; match bezier.handles { bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - overlay_context.line(handle, bezier.start); - overlay_context.line(handle, bezier.end); + overlay_context.line(handle, bezier.start, None); + overlay_context.line(handle, bezier.end, None); overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id))); } bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { if not_under_anchor(handle_start, bezier.start) { - overlay_context.line(handle_start, bezier.start); + overlay_context.line(handle_start, bezier.start, None); overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id))); } if not_under_anchor(handle_end, bezier.end) { - overlay_context.line(handle_end, bezier.end); + overlay_context.line(handle_end, bezier.end, None); overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id))); } } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 27fe689c..34064577 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -31,22 +31,22 @@ impl core::hash::Hash for OverlayContext { } impl OverlayContext { - pub fn quad(&mut self, quad: Quad) { + pub fn quad(&mut self, quad: Quad, color_fill: Option<&str>) { self.render_context.begin_path(); self.render_context.move_to(quad.0[3].x.round() - 0.5, quad.0[3].y.round() - 0.5); for i in 0..4 { self.render_context.line_to(quad.0[i].x.round() - 0.5, quad.0[i].y.round() - 0.5); } + if let Some(color_fill) = color_fill { + self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(color_fill)); + self.render_context.fill(); + } self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE)); self.render_context.stroke(); } - pub fn line(&mut self, start: DVec2, end: DVec2) { - self.dashed_line(start, end, None, None) - } - - pub fn colored_line(&mut self, start: DVec2, end: DVec2, color: &str) { - self.dashed_line(start, end, Some(color), None) + pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>) { + self.dashed_line(start, end, color, None) } pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option) { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 14522b39..006a0a55 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -299,32 +299,29 @@ impl NodeNetworkInterface { return None; }; match &mut node_template.persistent_node_metadata.node_type_metadata { - // TODO: Remove 2x2 offset and replace with layout system to find space for new node NodeTypePersistentMetadata::Layer(layer_metadata) => layer_metadata.position = LayerPosition::Absolute(position), NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Absolute(position), }; } - // Ensure a chain node has a selected downstream layer - if let NodeTypePersistentMetadata::Node(node_metadata) = &node_template.persistent_node_metadata.node_type_metadata { - if matches!(node_metadata.position, NodePosition::Chain) { - let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else { - log::error!("Could not get downstream layer in copy_nodes"); - return None; - }; - if new_ids.keys().all(|key| *key != downstream_layer.to_node()) { - let Some(position) = self.position(node_id, network_path) else { - log::error!("Could not get position in create_node_template"); - return None; - }; - node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::Node(NodePersistentMetadata { - position: NodePosition::Absolute(position + IVec2::new(2, 2)), - }); - } - } + // Ensure a chain node has a selected downstream layer, and set absolute nodes to a chain if there is a downstream layer + if self + .downstream_layer(node_id, network_path) + .map_or(true, |downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) + { + let Some(position) = self.position(node_id, network_path) else { + log::error!("Could not get position in create_node_template"); + return None; + }; + node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::Node(NodePersistentMetadata { + position: NodePosition::Absolute(position), + }); + } else if let NodeTypePersistentMetadata::Node(NodePersistentMetadata { position }) = &mut node_template.persistent_node_metadata.node_type_metadata { + *position = NodePosition::Chain; } // Shift all absolute nodes 2 to the right and 2 down + // TODO: Remove 2x2 offset and replace with layout system to find space for new node match &mut node_template.persistent_node_metadata.node_type_metadata { NodeTypePersistentMetadata::Layer(layer_metadata) => { if let LayerPosition::Absolute(position) = &mut layer_metadata.position { @@ -2966,25 +2963,18 @@ impl NodeNetworkInterface { if matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(input_to_disconnect, InputConnector::Export(_)) { self.disconnect_input(input_to_disconnect, network_path); } else if let Some(reconnect_input) = reconnect_to_input.take() { + let original_position = reconnect_input.as_node().and_then(|downstream_node| self.position(&downstream_node, network_path)); + let original_downstream_position = input_to_disconnect.node_id().and_then(|downstream_id| self.position(&downstream_id, network_path)); + self.set_input(input_to_disconnect, reconnect_input.clone(), network_path); - if let Some(node_metadata) = self.node_metadata(deleting_node_id, network_path) { - if let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata { - if let LayerPosition::Stack(deleted_layer_offset) = layer_metadata.position { - if let NodeInput::Node { node_id, .. } = reconnect_input { - self.set_stack_position(&node_id, deleted_layer_offset, network_path); - self.unload_upstream_node_click_targets(vec![node_id], network_path); - } - } else { - // Move upstream layer to the top of the stack - let Some(deleted_node_position) = self.position(deleting_node_id, network_path) else { - log::error!("Could not get position in remove_references_from_network"); - return false; - }; - if let NodeInput::Node { node_id, .. } = reconnect_input { - self.set_absolute_position(&node_id, deleted_node_position, network_path); - self.unload_upstream_node_click_targets(vec![node_id], network_path); - } - } + if let (Some(original_position), Some(original_downstream_position)) = (original_position, original_downstream_position) { + // Recalculate stack position (to keep layer in same place) if the upstream node is a layer in a chain + if reconnect_input + .as_node() + .is_some_and(|upstream_node| !self.is_absolute(&upstream_node, network_path) && self.is_layer(&upstream_node, network_path)) + { + let offset = (original_position.y - original_downstream_position.y - 3).max(0) as u32; + self.set_stack_position(&reconnect_input.as_node().unwrap(), offset, network_path); } } } else { @@ -3343,6 +3333,7 @@ impl NodeNetworkInterface { self.unload_all_nodes_bounding_box(network_path); } + /// Input connector is the input to the layer pub fn try_set_upstream_to_chain(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) { // If the new input is to a non layer node on the same y position as the input connector, or the input connector is the side input of a layer, then set it to chain position if let InputConnector::Node { @@ -3392,7 +3383,12 @@ impl NodeNetworkInterface { pub fn force_set_upstream_to_chain(&mut self, node_id: &NodeId, network_path: &[NodeId]) { for upstream_id in self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow).collect::>().iter() { - if !self.is_layer(upstream_id, network_path) && self.has_primary_output(node_id, network_path) { + if !self.is_layer(upstream_id, network_path) + && self.has_primary_output(node_id, network_path) + && self + .outward_wires(network_path) + .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(*upstream_id, 0)).is_some_and(|outward_wires| outward_wires.len() == 1)) + { self.set_chain_position(upstream_id, network_path); } // If there is an upstream layer then stop breaking the chain @@ -3404,6 +3400,10 @@ 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 { + log::error!("Could not get downstream layer in set_upstream_chain_to_absolute"); + return; + }; for upstream_id in self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow).collect::>().iter() { let Some(previous_position) = self.position(upstream_id, network_path) else { log::error!("Could not get position in set_to_node_or_layer"); @@ -3412,15 +3412,88 @@ impl NodeNetworkInterface { // Set any chain nodes to absolute positioning 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); } // If there is an upstream layer then stop breaking the chain else { break; } } - // Reload click target of the layer which used to 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); + } + + pub fn shift_selected_nodes(&mut self, mut node_ids: Vec, displacement_x: i32, mut displacement_y: i32, move_upstream: bool, network_path: &[NodeId]) { + if move_upstream { + for node_id in self.upstream_flow_back_from_nodes(node_ids.clone(), network_path, self::FlowType::UpstreamFlow).collect::>() { + if !node_ids.contains(&node_id) { + node_ids.push(node_id); + } + } + } + + let mut filtered_node_ids = Vec::new(); + for selected_node in &node_ids { + // 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())) + { + // Deselect stack nodes upstream from a selected layer + continue; + } + + // Deselect stack nodes upstream from a selected layer + let mut is_upstream_from_selected_absolute_layer = false; + let mut current_node = *selected_node; + loop { + if self.is_absolute(selected_node, network_path) { + break; + } + let Some(outward_wires) = self.outward_wires(network_path) else { + break; + }; + let Some(outward_wires) = outward_wires.get(&OutputConnector::node(current_node, 0)) else { + break; + }; + if outward_wires.is_empty() { + break; + } + let Some(downstream_node) = outward_wires[0].node_id() else { + break; + }; + if outward_wires[0].input_index() != 0 { + break; + } + if !self.is_layer(&downstream_node, network_path) { + break; + } + // Break the iteration if the layer is absolute(top of stack), or it is selected + if self.is_absolute(&downstream_node, network_path) || node_ids.contains(&downstream_node) { + is_upstream_from_selected_absolute_layer = node_ids.contains(&downstream_node); + break; + } + current_node = downstream_node; + } + if !is_upstream_from_selected_absolute_layer { + filtered_node_ids.push(*selected_node) + } + } + + // If a layer reaches the top of the stack, then stop moving up + for node_id in &filtered_node_ids { + let Some(node_metadata) = self.node_metadata(node_id, network_path) else { continue }; + let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata else { + continue; + }; + let LayerPosition::Stack(y_offset) = layer_metadata.position else { continue }; + if y_offset == 0 { + displacement_y = displacement_y.max(0); + } + } + + for node_id in filtered_node_ids { + self.shift_node(&node_id, IVec2::new(displacement_x, displacement_y), network_path); } } @@ -3433,6 +3506,14 @@ impl NodeNetworkInterface { if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata { if let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position { *layer_position += shift; + self.unload_upstream_node_click_targets(vec![*node_id], network_path); + for upstream_sibling in self + .upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::PrimaryFlow) + .take_while(|upstream_layer| self.is_layer(upstream_layer, network_path)) + .collect::>() + { + self.try_set_upstream_to_chain(&InputConnector::node(upstream_sibling, 1), network_path); + } } else if let LayerPosition::Stack(y_offset) = &mut layer_metadata.position { let shifted_y_offset = *y_offset as i32 + shift.y; // A layer can only be shifted to a positive y_offset @@ -3493,70 +3574,35 @@ impl NodeNetworkInterface { } } - // Disconnecting top of stack: - // 1. Get vertical offset 1 between the top of stack and the upstream layer - // 2. Set the position of the upstream layer to the top of stack - // 3. Remove the top of stack - // 4. Move top of stack to destination + let Some(layer_to_move_position) = self.position(&layer.to_node(), network_path) else { + log::error!("Could not get layer_to_move_position in move_layer_to_stack"); + return; + }; - // Disconnecting layer from stack: - // 1. Get vertical offset 1 between the layer and the upstream layer sibling - // 2. Get vertical offset 2 between the layer and the downstream layer sibling - // 3. Set the offset of the upstream layer to offset 2 - // 4. Remove the layer from the stack - // 5. Move the layer to the destination + let previous_upstream_node = self.upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::PrimaryFlow).nth(1); + let mut height_below_layer = 0; - let previous_upstream_layer = self - .upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::PrimaryFlow) - .skip(1) - .find(|node_id| self.is_layer(node_id, network_path)); - - let vertical_offset_1 = if let Some(previous_upstream_layer) = previous_upstream_layer { - let Some(node_metadata) = self.node_metadata(&previous_upstream_layer, network_path) else { - log::error!("Could not get node_metadata in move_layer_to_stack"); + if let Some(previous_upstream_node) = previous_upstream_node { + let Some(previous_upstream_node_position) = self.position(&previous_upstream_node, network_path) else { + log::error!("Could not get previous upstream node position in move_layer_to_stack"); return; }; - if let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position }) = &node_metadata.persistent_metadata.node_type_metadata { - match position { - LayerPosition::Stack(y_offset) => *y_offset, - _ => 0, - } - } else { - let (Some(moved_layer_position), Some(previous_upstream_layer_position)) = (self.position(&layer.to_node(), network_path), self.position(&previous_upstream_layer, network_path)) - else { - log::error!("Could not get moved layer position in move_layer_to_stack"); - return; - }; - (previous_upstream_layer_position.y - moved_layer_position.y - 3).max(0) as u32 - } - } else { - 0 - }; + height_below_layer = (previous_upstream_node_position.y - layer_to_move_position.y - 3).max(0) as u32; + } + let mut lowest_upstream_node_height = 0; + for upstream_node in self + .upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::LayerChildrenUpstreamFlow) + .collect::>() + { + let Some(upstream_node_position) = self.position(&upstream_node, network_path) else { + log::error!("Could not get upstream node position in move_layer_to_stack"); + return; + }; + lowest_upstream_node_height = lowest_upstream_node_height.max((upstream_node_position.y - layer_to_move_position.y).max(0) as u32); + } - let Some(moved_layer_metadata) = self.node_metadata(&layer.to_node(), network_path) else { - log::error!("Could not get node_metadata in move_layer_to_stack"); - return; - }; - - let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position }) = &moved_layer_metadata.persistent_metadata.node_type_metadata else { - log::error!("Could not get layer metadata for layer in move_layer_to_stack"); - return; - }; - - match &position { - LayerPosition::Stack(offset) => { - if let Some(previous_upstream_layer) = previous_upstream_layer { - self.set_stack_position(&previous_upstream_layer, *offset, network_path); - self.unload_upstream_node_click_targets(vec![previous_upstream_layer], network_path); - } - } - LayerPosition::Absolute(stack_top_position) => { - if let Some(previous_upstream_layer) = previous_upstream_layer { - self.set_absolute_position(&previous_upstream_layer, *stack_top_position, network_path); - self.unload_upstream_node_click_targets(vec![previous_upstream_layer], network_path); - } - } - }; + // Height under the layer to move, which should be retained after the move + let layer_to_move_height = height_below_layer.max(lowest_upstream_node_height); // If the moved layer is a child of the new parent, then get its index after the disconnect if let Some(moved_layer_previous_index) = parent.children(&self.document_metadata).position(|child| child == layer) { @@ -3570,43 +3616,42 @@ impl NodeNetworkInterface { self.remove_references_from_network(&layer.to_node(), true, network_path); self.disconnect_input(&InputConnector::node(layer.to_node(), 0), network_path); - // Moving layer to top of stack: - // 1. Get the position of the top of the stack. If a new stack is created, then offset by (-8, 3) from the parent - // 2. Move layer to the top of the stack - // 3. Connect layer to the top of stack - // 3. Set upstream layer to stack position with vertical offset 1 - - // Moving layer to stack: - // 1. Insert the layer into the stack - // 2. Get vertical offset 3 of the upstream layer - // 3. Set the stack offset of the moved layer to vertical offset 3 - // 4. Set the stack offset of the upstream layer to vertical offset 1 + // TODO: Collapse space between parent and second child if top of stack is moved using the layout system let post_node = ModifyInputsContext::get_post_node_with_index(self, parent, insert_index); - // // Get the previous input to the post node before inserting the layer + // Get the previous input to the post node before inserting the layer let Some(post_node_input) = self.input_from_connector(&post_node, network_path).cloned() else { log::error!("Could not get previous input in move_layer_to_stack for parent {parent:?} and insert_index {insert_index}"); return; }; + let Some(previous_layer_position) = self.position(&layer.to_node(), network_path) else { + log::error!("Could not get previous layer position in move_layer_to_stack"); + return; + }; + + let after_move_post_layer_position = if let Some(post_node_id) = post_node.node_id() { + self.position(&post_node_id, network_path) + } else { + Some(IVec2::new(8, -3)) + }; + + let Some(after_move_post_layer_position) = after_move_post_layer_position else { + log::error!("Could not get post node position in move_layer_to_stack"); + return; + }; + // Connect the layer to a parent layer/node at the top of the stack, or a non layer node midway down the stack if post_node.input_index() == 1 || matches!(post_node, InputConnector::Export(_)) || !post_node.node_id().is_some_and(|post_node_id| self.is_layer(&post_node_id, network_path)) { match post_node_input { // Create a new stack NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) => { self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path); - let post_node_position = if let Some(post_node_id) = post_node.node_id() { - self.position(&post_node_id, network_path) - } else { - Some(IVec2::ZERO) - }; - let Some(post_node_position) = post_node_position else { - log::error!("Could not get post node position in move_layer_to_stack"); - return; - }; - let offset = IVec2::new(-8, 3); - self.set_absolute_position(&layer.to_node(), post_node_position + offset, network_path); + + let final_layer_position = after_move_post_layer_position + IVec2::new(-8, 3); + let shift = final_layer_position - previous_layer_position; + self.shift_selected_nodes(vec![layer.to_node()], shift.x, shift.y, true, network_path); } // Move to the top of a stack NodeInput::Node { node_id, .. } => { @@ -3614,10 +3659,12 @@ impl NodeNetworkInterface { log::error!("Could not get top of stack position in move_layer_to_stack"); return; }; - self.set_absolute_position(&layer.to_node(), top_of_stack_position, network_path); + let shift = top_of_stack_position - previous_layer_position; + self.shift_selected_nodes(vec![layer.to_node()], shift.x, shift.y, true, network_path); self.unload_upstream_node_click_targets(vec![layer.to_node()], network_path); + self.shift_selected_nodes(vec![node_id], 0, layer_to_move_height as i32 + 3, true, network_path); self.insert_node_between(&layer.to_node(), &post_node, 0, network_path); - self.set_stack_position(&node_id, vertical_offset_1, network_path); + self.set_stack_position_calculated_offset(&node_id, &layer.to_node(), network_path); } NodeInput::Network { .. } => { log::error!("Cannot move post node to parent which connects to the imports") @@ -3627,8 +3674,11 @@ impl NodeNetworkInterface { match post_node_input { // Move to the bottom of the stack NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) => { + // TODO: Calculate height of bottom layer by getting height of upstream nodes instead of setting to 3 + let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3); + self.shift_selected_nodes(vec![layer.to_node()], offset.x, offset.y, true, network_path); self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path); - self.set_stack_position(&layer.to_node(), 0, network_path); + self.set_stack_position_calculated_offset(&layer.to_node(), &post_node.node_id().unwrap(), network_path); } // Insert into the stack NodeInput::Node { node_id: upstream_node_id, .. } => { @@ -3640,13 +3690,20 @@ impl NodeNetworkInterface { log::error!("Could not get upstream node metadata in move_layer_to_stack"); return; }; - let LayerPosition::Stack(vertical_offset_3) = position.clone() else { + // TODO: Max with height of upstream chain + let LayerPosition::Stack(post_node_height) = position.clone() else { log::error!("Could not get vertical offset 3 in move_layer_to_stack"); return; }; + let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3 + post_node_height as i32); + self.shift_selected_nodes(vec![layer.to_node()], offset.x, offset.y, true, network_path); + let upstream_offset = layer_to_move_height as i32 + 3; + self.shift_selected_nodes(vec![upstream_node_id], 0, upstream_offset, true, network_path); + self.insert_node_between(&layer.to_node(), &post_node, 0, network_path); - self.set_stack_position(&layer.to_node(), vertical_offset_3, network_path); - self.set_stack_position(&upstream_node_id, vertical_offset_1, network_path); + + self.set_stack_position_calculated_offset(&layer.to_node(), &post_node.node_id().unwrap(), network_path); + self.set_stack_position_calculated_offset(&upstream_node_id, &layer.to_node(), network_path); } NodeInput::Network { .. } => { log::error!("Cannot move post node to parent which connects to the imports") diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index 6de06b40..e11e3ca3 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -53,6 +53,8 @@ pub struct LayerPanelEntry { pub selected: bool, #[serde(rename = "inSelectedNetwork")] pub in_selected_network: bool, + #[serde(rename = "selectedParent")] + pub selected_parent: bool, } #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] diff --git a/editor/src/messages/tool/common_functionality/measure.rs b/editor/src/messages/tool/common_functionality/measure.rs index edaa080b..24074c79 100644 --- a/editor/src/messages/tool/common_functionality/measure.rs +++ b/editor/src/messages/tool/common_functionality/measure.rs @@ -20,7 +20,7 @@ pub fn overlay(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, if turn_x != selected_x { let min_viewport = transform.transform_point2(DVec2::new(turn_x.min(selected_x), turn_y)); let max_viewport = transform.transform_point2(DVec2::new(turn_x.max(selected_x), turn_y)); - overlay_context.line(min_viewport, max_viewport); + overlay_context.line(min_viewport, max_viewport, None); let length = format!("{:.2}", transform_to_document.transform_vector2(DVec2::X * (turn_x - selected_x)).length()); let direction = -(min_viewport - max_viewport).normalize_or_zero(); overlay_context.angle_text(&length, (min_viewport + max_viewport) / 2., direction, 5., utility_types::Pivot::TopCentreX); @@ -28,7 +28,7 @@ pub fn overlay(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, if turn_y != hovered_y { let min_viewport = transform.transform_point2(DVec2::new(turn_x, turn_y.min(hovered_y))); let max_viewport = transform.transform_point2(DVec2::new(turn_x, turn_y.max(hovered_y))); - overlay_context.line(min_viewport, max_viewport); + overlay_context.line(min_viewport, max_viewport, None); let length = format!("{:.2}", transform_to_document.transform_vector2(DVec2::Y * (turn_y - hovered_y)).length()); let direction = (min_viewport - max_viewport).normalize_or_zero().perp(); overlay_context.angle_text(&length, (min_viewport + max_viewport) / 2., direction, 5., utility_types::Pivot::LeftCentreY); diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 084f8b21..c20974dd 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -410,11 +410,12 @@ impl SnapManager { let start = DVec2::new(first.max().x, y); let end = DVec2::new(second.min().x, y); let signed_size = if bottom { y_size } else { -y_size }; - overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::Y * signed_size)); - overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::Y * signed_size)); + overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::Y * signed_size), None); + overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::Y * signed_size), None); overlay_context.line( transform.transform_point2(start + DVec2::Y * signed_size / 2.), transform.transform_point2(end + DVec2::Y * signed_size / 2.), + None, ); } } @@ -427,11 +428,12 @@ impl SnapManager { let start = DVec2::new(x, first.max().y); let end = DVec2::new(x, second.min().y); let signed_size = if right { x_size } else { -x_size }; - overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::X * signed_size)); - overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::X * signed_size)); + overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::X * signed_size), None); + overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::X * signed_size), None); overlay_context.line( transform.transform_point2(start + DVec2::X * signed_size / 2.), transform.transform_point2(end + DVec2::X * signed_size / 2.), + None, ); } } @@ -444,7 +446,7 @@ impl SnapManager { overlay_context.outline([Subpath::from_bezier(curve)].iter(), to_viewport); } if let Some(quad) = ind.target_bounds { - overlay_context.quad(to_viewport * quad); + overlay_context.quad(to_viewport * quad, None); } let viewport = to_viewport.transform_point2(ind.snapped_point_document); @@ -454,7 +456,7 @@ impl SnapManager { let align = [ind.alignment_target_x, ind.alignment_target_y].map(|target| target.map(|target| to_viewport.transform_point2(target))); let any_align = align.iter().flatten().next().is_some(); for &target in align.iter().flatten() { - overlay_context.line(viewport, target); + overlay_context.line(viewport, target, None); } for &target in align.iter().flatten() { overlay_context.manipulator_handle(target, false); diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 17f754aa..67f9db48 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -288,7 +288,7 @@ impl BoundingBoxManager { /// Update the position of the bounding box and transform handles pub fn render_overlays(&mut self, overlay_context: &mut OverlayContext) { - overlay_context.quad(self.transform * Quad::from_box(self.bounds)); + overlay_context.quad(self.transform * Quad::from_box(self.bounds), None); for position in self.evaluate_transform_handle_positions() { overlay_context.square(position, Some(6.), None, None); diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index fbb69321..6fcb16bf 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -259,7 +259,7 @@ impl Fsm for GradientToolFsmState { let Gradient { start, end, stops, .. } = gradient; let (start, end) = (transform.transform_point2(start), transform.transform_point2(end)); - overlay_context.line(start, end); + overlay_context.line(start, end, None); overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start)); overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End)); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 2e46710f..16f3f2ee 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -461,7 +461,12 @@ impl Fsm for PathToolFsmState { match self { Self::DrawingBox => { - overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position])); + let fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.05) + .rgba_hex(); + + overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position]), Some(&("#".to_string() + &fill_color))); } Self::Dragging => { tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index c82f52da..9749fb43 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -469,15 +469,15 @@ impl Fsm for PenToolFsmState { let valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; let next_point = transform.transform_point2(tool_data.next_point); let next_handle_start = transform.transform_point2(tool_data.next_handle_start); - overlay_context.line(next_point, next_handle_start); + overlay_context.line(next_point, next_handle_start, None); let start = tool_data.latest_point().map(|point| transform.transform_point2(point.pos)); let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start)); let handle_end = tool_data.handle_end.map(|point| transform.transform_point2(point)); if let (Some(start), Some(handle_start), Some(handle_end)) = (start, handle_start, handle_end) { - overlay_context.line(start, handle_start); - overlay_context.line(next_point, handle_end); + overlay_context.line(start, handle_start, None); + overlay_context.line(next_point, handle_end, None); path_overlays(document, shape_editor, &mut overlay_context); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 9349e14a..8211dda7 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -471,7 +471,11 @@ impl Fsm for SelectToolFsmState { } // Update the selection box - overlay_context.quad(quad); + let fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.05) + .rgba_hex(); + overlay_context.quad(quad, Some(&("#".to_string() + &fill_color))); } // Only highlight layers if the viewport is not being panned (middle mouse button is pressed) // TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 6669ee7d..8a554604 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -361,7 +361,7 @@ impl Fsm for TextToolFsmState { if far.x != 0. && far.y != 0. { let quad = Quad::from_box([DVec2::ZERO, far]); let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad; - overlay_context.quad(transformed_quad); + overlay_context.quad(transformed_quad, None); } } @@ -376,7 +376,7 @@ impl Fsm for TextToolFsmState { let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None); let quad = Quad::from_box([DVec2::ZERO, far]); let multiplied = document.metadata().transform_to_viewport(layer) * quad; - overlay_context.quad(multiplied); + overlay_context.quad(multiplied, None); } self diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index f52d5d28..ce2e177c 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -105,6 +105,16 @@ --color-error-red: #d6536e; --color-error-red-rgb: 214, 83, 110; + // Keep changes to these colors updated with `editor/src/consts.rs` + --color-overlay-blue: #00a8ff; + --color-overlay-blue-rgb: 0, 168, 255; + --color-overlay-yellow: #ffc848; + --color-overlay-yellow-rgb: 255, 200, 72; + --color-overlay-white: #ffffff; + --color-overlay-white-rgb: 255, 255, 255; + --color-overlay-gray: #cccccc; + --color-overlay-gray-rgb: 204, 204, 204; + --color-data-general: #c5c5c5; --color-data-general-dim: #767676; --color-data-raster: #e4bb72; diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 9ebdefbd..4dbbe1e4 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -375,7 +375,7 @@ class="layer" classes={{ selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : listing.entry.selected, - "in-selected-network": listing.entry.inSelectedNetwork, + "full-highlight": listing.entry.inSelectedNetwork && !listing.entry.selectedParent, "insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id, }} styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }} @@ -499,9 +499,11 @@ // Dimming &.selected { - background: #404040; + // Halfway between 3-darkgray and 4-dimgray (this interpolation approach only works on grayscale values) + --component: calc((Max(var(--color-3-darkgray-rgb)) + Max(var(--color-4-dimgray-rgb))) / 2); + background: rgb(var(--component), var(--component), var(--component)); - &.in-selected-network { + &.full-highlight { background: var(--color-4-dimgray); } } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 45a1fba0..2dcccbef 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -1027,7 +1027,7 @@ // ancestor elements, `.graph` and `.panel`, each have the simultaneous pairing of `overflow: hidden` and `border-radius`. // See: https://stackoverflow.com/questions/75137879/bug-with-backdrop-filter-in-firefox // backdrop-filter: blur(4px); - background: rgba(0, 0, 0, 0.33); + background: rgba(var(--color-0-black-rgb), 0.33); .node-error { position: absolute; @@ -1161,11 +1161,10 @@ } &.selected { - // This is the result of blending `rgba(255, 255, 255, 0.1)` over `rgba(0, 0, 0, 0.33)` - background: rgba(66, 66, 66, 0.4); + background: rgba(var(--color-5-dullgray-rgb), 0.5); &.in-selected-network { - background: rgba(80, 80, 80, 0.5); + background: rgba(var(--color-6-lowergray-rgb), 0.5); } } @@ -1259,18 +1258,18 @@ &.selected { .primary { - background: rgba(255, 255, 255, 0.15); + background: rgba(var(--color-f-white-rgb), 0.15); &.in-selected-network { - background: rgba(255, 255, 255, 0.2); + background: rgba(var(--color-f-white-rgb), 0.2); } } .parameters { - background: rgba(255, 255, 255, 0.1); + background: rgba(var(--color-f-white-rgb), 0.1); &.in-selected-network { - background: rgba(255, 255, 255, 0.15); + background: rgba(var(--color-f-white-rgb), 0.15); } } } @@ -1296,7 +1295,7 @@ width: 100%; height: 24px; border-radius: 2px 2px 0 0; - background: rgba(255, 255, 255, 0.05); + background: rgba(var(--color-f-white-rgb), 0.05); &.no-parameter-section { border-radius: 2px; @@ -1359,9 +1358,9 @@ .box-selection { position: absolute; - z-index: 2; - background-color: rgba(77, 168, 221, 0.2); - border: 1px solid rgba(77, 168, 221); pointer-events: none; + background: rgba(var(--color-overlay-blue-rgb), 0.05); + border: 1px solid var(--color-overlay-blue); + z-index: 2; } diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 94445b05..c28f03bf 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -888,6 +888,8 @@ export class LayerPanelEntry { selected!: boolean; inSelectedNetwork!: boolean; + + selectedParent!: boolean; } export class DisplayDialogDismiss extends JsMessage {} diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index c14e5916..a2fa7d6a 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -125,7 +125,7 @@ impl<'a, I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe> Executo Ok(result) => result.map_err(|e| e.into()), Err(e) => { Box::leak(e); - Err("Node graph execution paniced".into()) + Err("Node graph execution panicked".into()) } } })