From c4f4e899508799013f97845e0b00722f04a3a481 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Sun, 2 Nov 2025 15:27:03 -0500 Subject: [PATCH 1/9] transcribe 0hypercube's frontend help code --- editor/src/messages/frontend/frontend_message.rs | 4 ++++ .../document/node_graph/utility_types.rs | 16 ++++++++++++++++ frontend/src/components/views/Graph.svelte | 16 ++++++++++++++++ frontend/src/messages.ts | 9 +++++++++ frontend/src/state-providers/node-graph.ts | 9 +++++++++ 5 files changed, 54 insertions(+) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 0195180f1b..8fb331fa9e 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -152,6 +152,10 @@ pub enum FrontendMessage { #[serde(rename = "box")] box_selection: Option, }, + UpdateLasoo { + #[serde(rename = "lasoo")] + lasoo_selection: Option, + }, UpdateContextMenuInformation { #[serde(rename = "contextMenuInformation")] context_menu_information: Option, diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 60869acadc..1ac58e165c 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -152,6 +152,22 @@ pub struct BoxSelection { pub end_y: u32, } +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct LasooSelection { + pub points: String, +} + +impl FromIterator for LasooSelection { + fn from_iter>(iter: I) -> Self { + let mut points = String::new(); + for coordinate in iter { + use std::fmt::Write; + write!(&mut points, "{},{} ", coordinate.x, coordinate.y).unwrap(); + } + LasooSelection { points } + } +} + #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[serde(tag = "type", content = "data")] pub enum ContextMenuData { diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 819d309229..4ffa748b59 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -777,6 +777,12 @@ > {/if} +{#if $nodeGraph.lasoo} + + + +{/if} + diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index c7320bfadf..ac53c235a1 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -29,6 +29,10 @@ export class UpdateBox extends JsMessage { readonly box!: Box | undefined; } +export class UpdateLasoo extends JsMessage { + readonly lasoo!: Lasoo | undefined; +} + export class UpdateClickTargets extends JsMessage { readonly clickTargets!: FrontendClickTargets | undefined; } @@ -154,6 +158,10 @@ export class Box { readonly endY!: number; } +export class Lasoo { + readonly points!: string; +} + export type FrontendClickTargets = { readonly nodeClickTargets: string[]; readonly layerClickTargets: string[]; @@ -1665,6 +1673,7 @@ export const messageMakers: Record = { TriggerVisitLink, UpdateActiveDocument, UpdateBox, + UpdateLasoo, UpdateClickTargets, UpdateContextMenuInformation, UpdateDialogButtons, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index 80d35eadb8..451b022074 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -4,6 +4,7 @@ import { type Editor } from "@graphite/editor"; import type { NodeGraphError } from "@graphite/messages"; import { type Box, + type Lasoo, type FrontendClickTargets, type ContextMenuInformation, type FrontendNode, @@ -12,6 +13,7 @@ import { ClearAllNodeGraphWires, SendUIMetadata, UpdateBox, + UpdateLasoo, UpdateClickTargets, UpdateContextMenuInformation, UpdateInSelectedNetwork, @@ -32,6 +34,7 @@ import { export function createNodeGraphState(editor: Editor) { const { subscribe, update } = writable({ box: undefined as Box | undefined, + lasoo: undefined as Lasoo | undefined, clickTargets: undefined as FrontendClickTargets | undefined, contextMenuInformation: undefined as ContextMenuInformation | undefined, error: undefined as NodeGraphError | undefined, @@ -68,6 +71,12 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(UpdateLasoo, (updateLasoo) => { + update((state) => { + state.lasoo = updateLasoo.lasoo; + return state; + }); + }); editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (UpdateClickTargets) => { update((state) => { state.clickTargets = UpdateClickTargets.clickTargets; From c880368e90306f0166acba4d86d04f69d862becd Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Sun, 2 Nov 2025 15:27:56 -0500 Subject: [PATCH 2/9] change lasoo to lasso --- editor/src/messages/frontend/frontend_message.rs | 8 ++++---- .../portfolio/document/node_graph/utility_types.rs | 6 +++--- frontend/src/components/views/Graph.svelte | 8 ++++---- frontend/src/messages.ts | 8 ++++---- frontend/src/state-providers/node-graph.ts | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 8fb331fa9e..1060420e11 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -2,7 +2,7 @@ use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument}; use crate::messages::app_window::app_window_message_handler::AppWindowPlatform; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::{ - BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform, + BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, LassoSelection, NodeGraphErrorDiagnostic, Transform, }; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; @@ -152,9 +152,9 @@ pub enum FrontendMessage { #[serde(rename = "box")] box_selection: Option, }, - UpdateLasoo { - #[serde(rename = "lasoo")] - lasoo_selection: Option, + UpdateLasso { + #[serde(rename = "lasso")] + lasso_selection: Option, }, UpdateContextMenuInformation { #[serde(rename = "contextMenuInformation")] diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 1ac58e165c..5e1c269391 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -153,18 +153,18 @@ pub struct BoxSelection { } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct LasooSelection { +pub struct LassoSelection { pub points: String, } -impl FromIterator for LasooSelection { +impl FromIterator for LassoSelection { fn from_iter>(iter: I) -> Self { let mut points = String::new(); for coordinate in iter { use std::fmt::Write; write!(&mut points, "{},{} ", coordinate.x, coordinate.y).unwrap(); } - LasooSelection { points } + LassoSelection { points } } } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 4ffa748b59..5252f82e0e 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -777,9 +777,9 @@ > {/if} -{#if $nodeGraph.lasoo} - - +{#if $nodeGraph.lasso} + + {/if} @@ -1376,7 +1376,7 @@ border: 1px solid #00a8ff; } - .lasoo-selection { + .lasso-selection { position: absolute; pointer-events: none; z-index: 2; diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index ac53c235a1..c2969c426d 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -29,8 +29,8 @@ export class UpdateBox extends JsMessage { readonly box!: Box | undefined; } -export class UpdateLasoo extends JsMessage { - readonly lasoo!: Lasoo | undefined; +export class UpdateLasso extends JsMessage { + readonly lasso!: Lasso | undefined; } export class UpdateClickTargets extends JsMessage { @@ -158,7 +158,7 @@ export class Box { readonly endY!: number; } -export class Lasoo { +export class Lasso { readonly points!: string; } @@ -1673,7 +1673,7 @@ export const messageMakers: Record = { TriggerVisitLink, UpdateActiveDocument, UpdateBox, - UpdateLasoo, + UpdateLasso, UpdateClickTargets, UpdateContextMenuInformation, UpdateDialogButtons, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index 451b022074..063dba3781 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -4,7 +4,7 @@ import { type Editor } from "@graphite/editor"; import type { NodeGraphError } from "@graphite/messages"; import { type Box, - type Lasoo, + type Lasso, type FrontendClickTargets, type ContextMenuInformation, type FrontendNode, @@ -13,7 +13,7 @@ import { ClearAllNodeGraphWires, SendUIMetadata, UpdateBox, - UpdateLasoo, + UpdateLasso, UpdateClickTargets, UpdateContextMenuInformation, UpdateInSelectedNetwork, @@ -34,7 +34,7 @@ import { export function createNodeGraphState(editor: Editor) { const { subscribe, update } = writable({ box: undefined as Box | undefined, - lasoo: undefined as Lasoo | undefined, + lasso: undefined as Lasso | undefined, clickTargets: undefined as FrontendClickTargets | undefined, contextMenuInformation: undefined as ContextMenuInformation | undefined, error: undefined as NodeGraphError | undefined, @@ -71,9 +71,9 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); - editor.subscriptions.subscribeJsMessage(UpdateLasoo, (updateLasoo) => { + editor.subscriptions.subscribeJsMessage(UpdateLasso, (updateLasso) => { update((state) => { - state.lasoo = updateLasoo.lasoo; + state.lasso = updateLasso.lasso; return state; }); }); From 52a3bbfa5cba58f4e03f5c526230b03f12af2911 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Sun, 2 Nov 2025 21:17:48 -0500 Subject: [PATCH 3/9] finish adding node graph lasso select (also with alt-subtract) --- .../messages/input_mapper/input_mappings.rs | 1 + .../document/node_graph/node_graph_message.rs | 1 + .../node_graph/node_graph_message_handler.rs | 128 +++++++++++++++++- 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index ce278ea7f2..622de5f184 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -58,6 +58,7 @@ pub fn input_mappings() -> Mapping { 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=[Accel, Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: true, 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), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 330d123efe..0a60626dc7 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -221,6 +221,7 @@ pub enum NodeGraphMessage { }, UpdateEdges, UpdateBoxSelection, + UpdateLassoSelection, UpdateImportsExports, UpdateLayerPanel, UpdateNewNodeGraph, 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 85c7fde361..b955c2f9ea 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 @@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::document_message_handler::navigation_controls; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; -use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, NodeGraphErrorDiagnostic}; +use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, LassoSelection, NodeGraphErrorDiagnostic}; 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::{ @@ -25,8 +25,9 @@ use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graphene_std::math::math_ext::QuadExt; use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath; +use graphene_std::vector::misc::dvec2_to_point; use graphene_std::*; -use kurbo::{DEFAULT_ACCURACY, Shape}; +use kurbo::{DEFAULT_ACCURACY, Line, PathSeg, Shape}; use renderer::Quad; use std::cmp::Ordering; @@ -64,6 +65,9 @@ pub struct NodeGraphMessageHandler { /// 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)>, + /// If dragging the background to create a lasso selection, this stores its current lasso polygon in node graph coordinates, + /// plus a flag indicating if it has been dragged since the mousedown began. + lasso_selection_curr: Option<(Vec, bool)>, /// Restore the selection before box selection if it is aborted selection_before_pointer_down: Vec, /// If the grip icon is held during a drag, then shift without pushing other nodes @@ -765,6 +769,15 @@ impl<'a> MessageHandler> for NodeG responses.add(FrontendMessage::UpdateBox { box_selection: None }); return; } + // Abort a lasso selection + if self.lasso_selection_curr.is_some() { + self.lasso_selection_curr = None; + responses.add(NodeGraphMessage::SelectedNodesSet { + nodes: self.selection_before_pointer_down.clone(), + }); + responses.add(FrontendMessage::UpdateLasso { lasso_selection: None }); + return; + } // Abort dragging a wire if self.wire_in_progress_from_connector.is_some() { self.wire_in_progress_from_connector = None; @@ -974,7 +987,13 @@ impl<'a> MessageHandler> for NodeG if !shift_click && !alt_click { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() }) } - self.box_selection_start = Some((node_graph_point, false)); + + if control_click { + self.lasso_selection_curr = Some((vec![node_graph_point], false)); + } else { + self.box_selection_start = Some((node_graph_point, false)); + } + self.update_node_graph_hints(responses); } NodeGraphMessage::PointerMove { shift } => { @@ -1109,6 +1128,10 @@ impl<'a> MessageHandler> for NodeG *box_selection_dragged = true; responses.add(NodeGraphMessage::UpdateBoxSelection); self.update_node_graph_hints(responses); + } else if let Some((_, lasso_selection_dragged)) = &mut self.lasso_selection_curr { + *lasso_selection_dragged = true; + responses.add(NodeGraphMessage::UpdateLassoSelection); + self.update_node_graph_hints(responses); } else if self.reordering_import.is_some() { let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { log::error!("Could not get modify import export in PointerMove"); @@ -1391,6 +1414,7 @@ impl<'a> MessageHandler> for NodeG self.drag_start = None; self.begin_dragging = false; self.box_selection_start = None; + self.lasso_selection_curr = None; self.wire_in_progress_from_connector = None; self.wire_in_progress_type = FrontendGraphDataType::General; self.wire_in_progress_to_connector = None; @@ -1399,12 +1423,17 @@ impl<'a> MessageHandler> for NodeG responses.add(DocumentMessage::EndTransaction); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); responses.add(FrontendMessage::UpdateBox { box_selection: None }); + responses.add(FrontendMessage::UpdateLasso { lasso_selection: None }); responses.add(FrontendMessage::UpdateImportReorderIndex { index: None }); responses.add(FrontendMessage::UpdateExportReorderIndex { index: None }); self.update_node_graph_hints(responses); } NodeGraphMessage::PointerOutsideViewport { shift } => { - if self.drag_start.is_some() || self.box_selection_start.is_some() || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) { + if self.drag_start.is_some() + || self.box_selection_start.is_some() + || self.lasso_selection_curr.is_some() + || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) + { let _ = self.auto_panning.shift_viewport(ipp, viewport, responses); } else { // Auto-panning @@ -1956,6 +1985,88 @@ impl<'a> MessageHandler> for NodeG responses.add(FrontendMessage::UpdateBox { box_selection }) } } + NodeGraphMessage::UpdateLassoSelection => { + if let Some((lasso_selection_curr, _)) = &mut self.lasso_selection_curr { + // WARNING WARNING WARNING: this commented-out code is copy pasted from UpdateBoxSelection above and has not been edited for lasso + // The mouse button was released but we missed the pointer up event + // if ((e.buttons & 1) === 0) { + // completeBoxSelection(); + // boxSelection = undefined; + // } else if ((e.buttons & 2) !== 0) { + // editor.handle.selectNodes(new BigUint64Array(previousSelection)); + // boxSelection = undefined; + // } + + let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { + log::error!("Could not get network metadata in UpdateBoxSelection"); + return; + }; + + { + let node_graph_point = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(ipp.mouse.position); + + lasso_selection_curr.push(node_graph_point); + } + + let lasso_selection_viewport: Vec = lasso_selection_curr + .iter() + .map(|selection_point| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(*selection_point)) + .collect(); + + let shift = ipp.keyboard.get(Key::Shift as usize); + let alt = ipp.keyboard.get(Key::Alt as usize); + let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { + log::error!("Could not get selected nodes in UpdateBoxSelection"); + return; + }; + let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::>(); + let mut nodes = if shift || alt { + selected_nodes.selected_nodes_ref().iter().cloned().collect::>() + } else { + HashSet::new() + }; + let all_nodes = network_metadata.persistent_metadata.node_metadata.keys().cloned().collect::>(); + let path: Vec = { + fn points_to_polygon(points: &[DVec2]) -> Vec { + points + .windows(2) + .map(|w| PathSeg::Line(Line::new(dvec2_to_point(w[0]), dvec2_to_point(w[1])))) + .chain(std::iter::once(PathSeg::Line(Line::new( + dvec2_to_point(*points.last().unwrap()), + dvec2_to_point(*points.first().unwrap()), + )))) + .collect() + } + points_to_polygon(lasso_selection_curr) + }; + for node_id in all_nodes { + let Some(click_targets) = network_interface.node_click_targets(&node_id, selection_network_path) else { + log::error!("Could not get transient metadata for node {node_id}"); + continue; + }; + if click_targets.node_click_target.intersect_path(|| path.iter().cloned(), DAffine2::IDENTITY) { + if alt { + nodes.remove(&node_id); + } else { + nodes.insert(node_id); + } + } + } + if nodes != previous_selection { + responses.add(NodeGraphMessage::SelectedNodesSet { + nodes: nodes.into_iter().collect::>(), + }); + } + responses.add(FrontendMessage::UpdateLasso { + lasso_selection: Some(LassoSelection::from_iter(lasso_selection_viewport.into_iter())), + }) + } + } NodeGraphMessage::UpdateImportsExports => { let imports = network_interface.frontend_imports(breadcrumb_network_path); let exports = network_interface.frontend_exports(breadcrumb_network_path); @@ -2711,11 +2822,12 @@ impl NodeGraphMessageHandler { // Node gragging is in progress (having already moved at least one pixel from the mouse down position) let dragging_nodes = self.drag_start.as_ref().is_some_and(|(_, dragged)| *dragged); - // A box selection is in progress - let dragging_box_selection = self.box_selection_start.is_some_and(|(_, box_selection_dragged)| box_selection_dragged); + // A box or lasso selection is in progress + let dragging_selection = self.box_selection_start.as_ref().is_some_and(|(_, box_selection_dragged)| *box_selection_dragged) + || self.lasso_selection_curr.as_ref().is_some_and(|(_, lasso_selection_dragged)| *lasso_selection_dragged); // Cancel the ongoing action - if wiring || dragging_nodes || dragging_box_selection { + if wiring || dragging_nodes || dragging_selection { let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); return; @@ -2760,6 +2872,7 @@ impl Default for NodeGraphMessageHandler { node_has_moved_in_drag: false, shift_without_push: false, box_selection_start: None, + lasso_selection_curr: None, drag_start_chain_nodes: Vec::new(), selection_before_pointer_down: Vec::new(), disconnecting: None, @@ -2790,6 +2903,7 @@ impl PartialEq for NodeGraphMessageHandler { && self.begin_dragging == other.begin_dragging && self.node_has_moved_in_drag == other.node_has_moved_in_drag && self.box_selection_start == other.box_selection_start + && self.lasso_selection_curr == other.lasso_selection_curr && self.initial_disconnecting == other.initial_disconnecting && self.select_if_not_dragged == other.select_if_not_dragged && self.wire_in_progress_from_connector == other.wire_in_progress_from_connector From 4f14e210a5e6088118dcf851d8c9f7874e2fb126 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Sun, 2 Nov 2025 22:10:17 -0500 Subject: [PATCH 4/9] add hint for lasso select --- .../portfolio/document/node_graph/node_graph_message_handler.rs | 1 + 1 file changed, 1 insertion(+) 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 b955c2f9ea..65f06289a1 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 @@ -2841,6 +2841,7 @@ impl NodeGraphMessageHandler { HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), HintInfo::keys([Key::Shift], "Extend").prepend_plus(), HintInfo::keys([Key::Alt], "Subtract").prepend_plus(), + HintInfo::keys([Key::Accel], "Lasso").prepend_plus(), ]), ]); if self.has_selection { From 3f14023bb3c071e3117f47607638cd3922d74652 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Mon, 24 Nov 2025 16:35:28 -0500 Subject: [PATCH 5/9] remove lasso_selection_curr drag flag and extraneous comments --- .../node_graph/node_graph_message_handler.rs | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) 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 65f06289a1..dae8fb7989 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 @@ -64,10 +64,12 @@ pub struct NodeGraphMessageHandler { pub drag_start_chain_nodes: Vec, /// 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. + /// (We should only update hints when it has been dragged after the initial mousedown.) box_selection_start: Option<(DVec2, bool)>, - /// If dragging the background to create a lasso selection, this stores its current lasso polygon in node graph coordinates, - /// plus a flag indicating if it has been dragged since the mousedown began. - lasso_selection_curr: Option<(Vec, bool)>, + /// If dragging the background to create a lasso selection, this stores its current lasso polygon in node graph coordinates. + /// Notice that it has been dragged since the mousedown began iff the polygon has at least two points. + /// (We should only update hints when it has been dragged after the initial mousedown.) + lasso_selection_curr: Option>, /// Restore the selection before box selection if it is aborted selection_before_pointer_down: Vec, /// If the grip icon is held during a drag, then shift without pushing other nodes @@ -989,7 +991,7 @@ impl<'a> MessageHandler> for NodeG } if control_click { - self.lasso_selection_curr = Some((vec![node_graph_point], false)); + self.lasso_selection_curr = Some(vec![node_graph_point]); } else { self.box_selection_start = Some((node_graph_point, false)); } @@ -1128,8 +1130,7 @@ impl<'a> MessageHandler> for NodeG *box_selection_dragged = true; responses.add(NodeGraphMessage::UpdateBoxSelection); self.update_node_graph_hints(responses); - } else if let Some((_, lasso_selection_dragged)) = &mut self.lasso_selection_curr { - *lasso_selection_dragged = true; + } else if self.lasso_selection_curr.is_some() { responses.add(NodeGraphMessage::UpdateLassoSelection); self.update_node_graph_hints(responses); } else if self.reordering_import.is_some() { @@ -1921,15 +1922,6 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::UpdateBoxSelection => { if let Some((box_selection_start, _)) = self.box_selection_start { - // The mouse button was released but we missed the pointer up event - // if ((e.buttons & 1) === 0) { - // completeBoxSelection(); - // boxSelection = undefined; - // } else if ((e.buttons & 2) !== 0) { - // editor.handle.selectNodes(new BigUint64Array(previousSelection)); - // boxSelection = undefined; - // } - let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { log::error!("Could not get network metadata in UpdateBoxSelection"); return; @@ -1986,17 +1978,7 @@ impl<'a> MessageHandler> for NodeG } } NodeGraphMessage::UpdateLassoSelection => { - if let Some((lasso_selection_curr, _)) = &mut self.lasso_selection_curr { - // WARNING WARNING WARNING: this commented-out code is copy pasted from UpdateBoxSelection above and has not been edited for lasso - // The mouse button was released but we missed the pointer up event - // if ((e.buttons & 1) === 0) { - // completeBoxSelection(); - // boxSelection = undefined; - // } else if ((e.buttons & 2) !== 0) { - // editor.handle.selectNodes(new BigUint64Array(previousSelection)); - // boxSelection = undefined; - // } - + if let Some(lasso_selection_curr) = &mut self.lasso_selection_curr { let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { log::error!("Could not get network metadata in UpdateBoxSelection"); return; @@ -2824,7 +2806,7 @@ impl NodeGraphMessageHandler { // A box or lasso selection is in progress let dragging_selection = self.box_selection_start.as_ref().is_some_and(|(_, box_selection_dragged)| *box_selection_dragged) - || self.lasso_selection_curr.as_ref().is_some_and(|(_, lasso_selection_dragged)| *lasso_selection_dragged); + || self.lasso_selection_curr.as_ref().is_some_and(|lasso_selection| lasso_selection.len() >= 2); // Cancel the ongoing action if wiring || dragging_nodes || dragging_selection { From 31e3508340413a78c0a982d52c6ca5d20a6019b5 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Mon, 24 Nov 2025 17:35:59 -0500 Subject: [PATCH 6/9] refer to correct scope in error messages --- .../document/node_graph/node_graph_message_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 dae8fb7989..bd36c9ee0b 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 @@ -1980,7 +1980,7 @@ impl<'a> MessageHandler> for NodeG NodeGraphMessage::UpdateLassoSelection => { if let Some(lasso_selection_curr) = &mut self.lasso_selection_curr { let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { - log::error!("Could not get network metadata in UpdateBoxSelection"); + log::error!("Could not get network metadata in UpdateLassoSelection"); return; }; @@ -2003,7 +2003,7 @@ impl<'a> MessageHandler> for NodeG let shift = ipp.keyboard.get(Key::Shift as usize); let alt = ipp.keyboard.get(Key::Alt as usize); let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { - log::error!("Could not get selected nodes in UpdateBoxSelection"); + log::error!("Could not get selected nodes in UpdateLassoSelection"); return; }; let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::>(); From 66abc57437d9b721123e9f1ed6883322b6a13809 Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Mon, 24 Nov 2025 18:06:12 -0500 Subject: [PATCH 7/9] remove one collect after appeasing the borrow checker --- .../node_graph/node_graph_message_handler.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 bd36c9ee0b..5854c245ad 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 @@ -1995,10 +1995,11 @@ impl<'a> MessageHandler> for NodeG lasso_selection_curr.push(node_graph_point); } - let lasso_selection_viewport: Vec = lasso_selection_curr - .iter() - .map(|selection_point| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(*selection_point)) - .collect(); + responses.add(FrontendMessage::UpdateLasso { + lasso_selection: Some(LassoSelection::from_iter(lasso_selection_curr.iter().map(|selection_point| { + network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(*selection_point) + }))), + }); let shift = ipp.keyboard.get(Key::Shift as usize); let alt = ipp.keyboard.get(Key::Alt as usize); @@ -2044,9 +2045,6 @@ impl<'a> MessageHandler> for NodeG nodes: nodes.into_iter().collect::>(), }); } - responses.add(FrontendMessage::UpdateLasso { - lasso_selection: Some(LassoSelection::from_iter(lasso_selection_viewport.into_iter())), - }) } } NodeGraphMessage::UpdateImportsExports => { From 94270621d04f5b1c96d4fda5d03d65a518c48e2d Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Mon, 24 Nov 2025 18:13:49 -0500 Subject: [PATCH 8/9] make node_graph_to_viewport less verbose (dedup dot chains) --- .../node_graph/node_graph_message_handler.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) 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 5854c245ad..5386823184 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 @@ -1984,21 +1984,15 @@ impl<'a> MessageHandler> for NodeG return; }; - { - let node_graph_point = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(ipp.mouse.position); + let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; + let viewport_to_node_graph = node_graph_to_viewport.inverse(); - lasso_selection_curr.push(node_graph_point); - } + lasso_selection_curr.push(viewport_to_node_graph.transform_point2(ipp.mouse.position)); responses.add(FrontendMessage::UpdateLasso { - lasso_selection: Some(LassoSelection::from_iter(lasso_selection_curr.iter().map(|selection_point| { - network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(*selection_point) - }))), + lasso_selection: Some(LassoSelection::from_iter( + lasso_selection_curr.iter().map(|selection_point| node_graph_to_viewport.transform_point2(*selection_point)), + )), }); let shift = ipp.keyboard.get(Key::Shift as usize); From d3aca58733307c525587c2d201317aa62791ca8d Mon Sep 17 00:00:00 2001 From: wade-cheng Date: Tue, 25 Nov 2025 22:45:43 -0500 Subject: [PATCH 9/9] add missing extend/subtract hints during node selection --- .../document/node_graph/node_graph_message_handler.rs | 8 +++++++- editor/src/messages/tool/tool_messages/select_tool.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) 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 5386823184..f797b8270c 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 @@ -2802,7 +2802,13 @@ impl NodeGraphMessageHandler { // Cancel the ongoing action if wiring || dragging_nodes || dragging_selection { - let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]); + let hint_data = HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Shift], "Extend"), HintInfo::keys([Key::Alt], "Subtract")]), + // TODO: Re-select deselected layers during drag when Shift is pressed, and re-deselect if Shift is released before drag ends. + // TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048) + // TODO: (Also remember to do this for the select tool; grep for these todo comments.) + ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); return; } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 938b5ff6f9..b329fd177a 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1754,7 +1754,7 @@ impl Fsm for SelectToolFsmState { HintGroup(vec![HintInfo::keys([Key::Shift], "Extend"), HintInfo::keys([Key::Alt], "Subtract")]), // TODO: Re-select deselected layers during drag when Shift is pressed, and re-deselect if Shift is released before drag ends. // TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048) - // HintGroup(vec![HintInfo::keys([Key::Shift], "Extend")]) + // TODO: (Also remember to do this for the node graph; grep for these todo comments.) ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); }