From 43a78e8212c503cbaa95f3b4411462f5d2bd65b2 Mon Sep 17 00:00:00 2001 From: Zakarum Date: Tue, 17 Sep 2024 21:21:30 +0200 Subject: [PATCH] Ability to fetch list of selected nodes. Keep order of selection. Fixes a few issues with removed nodes --- examples/demo.rs | 52 ++++++++++++- src/ui.rs | 26 ++++--- src/ui/state.rs | 187 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 222 insertions(+), 43 deletions(-) diff --git a/examples/demo.rs b/examples/demo.rs index b79d79b..2f583a9 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use eframe::{App, CreationContext}; -use egui::{Color32, Ui}; +use egui::{Color32, Id, Ui}; use egui_snarl::{ ui::{AnyPins, PinInfo, SnarlStyle, SnarlViewer, WireStyle}, InPin, InPinId, NodeId, OutPin, OutPinId, Snarl, @@ -34,6 +34,16 @@ enum DemoNode { } impl DemoNode { + fn name(&self) -> &str { + match self { + DemoNode::Sink => "Sink", + DemoNode::Number(_) => "Number", + DemoNode::String(_) => "String", + DemoNode::ShowImage(_) => "ShowImage", + DemoNode::ExprNode(_) => "ExprNode", + } + } + fn number_out(&self) -> f64 { match self { DemoNode::Number(value) => *value, @@ -927,6 +937,7 @@ impl Expr { pub struct DemoApp { snarl: Snarl, style: SnarlStyle, + snarl_ui_id: Option, } impl DemoApp { @@ -953,7 +964,7 @@ impl DemoApp { }; // let style = SnarlStyle::new(); - DemoApp { snarl, style } + DemoApp { snarl, style, snarl_ui_id: None } } } @@ -987,9 +998,44 @@ impl App for DemoApp { }); }); + if let Some(snarl_ui_id) = self.snarl_ui_id { + egui::SidePanel::right("selected-list").show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + ui.strong("Selected nodes"); + + let selected = Snarl::::get_selected_nodes_at("snarl", snarl_ui_id, ui.ctx()); + let mut selected = selected + .into_iter() + .map(|id| (id, &self.snarl[id])) + .collect::>(); + + selected.sort_by_key(|(id, _)| *id); + + let mut remove = None; + + for (id, node) in selected { + ui.horizontal(|ui| { + ui.label(format!("{:?}", id)); + ui.label(node.name()); + ui.add_space(ui.spacing().item_spacing.x); + if ui.button("Remove").clicked() { + remove = Some(id); + } + }); + } + + if let Some(id) = remove { + self.snarl.remove_node(id); + } + }); + }); + } + egui::CentralPanel::default().show(ctx, |ui| { + self.snarl_ui_id = Some(ui.id()); + self.snarl - .show(&mut DemoViewer, &self.style, egui::Id::new("snarl"), ui); + .show(&mut DemoViewer, &self.style, "snarl", ui); }); } diff --git a/src/ui.rs b/src/ui.rs index 0ea2902..959cc85 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -599,6 +599,10 @@ impl Snarl { let mut node_rects = Vec::new(); for node_idx in draw_order { + if !self.nodes.contains(node_idx.0) { + continue; + } + // show_node(node_idx); let response = self.draw_node( ui, @@ -941,24 +945,28 @@ impl Snarl { ui.advance_cursor_after_rect(Rect::from_min_size(viewport.min, Vec2::ZERO)); if let Some(node) = node_to_top { - ui.ctx().request_repaint(); - snarl_state.node_to_top(node); + if self.nodes.contains(node.0) { + ui.ctx().request_repaint(); + snarl_state.node_to_top(node); + } } if let Some((node, delta)) = node_moved { - ui.ctx().request_repaint(); - if snarl_state.selected_nodes().contains(&node) { - for node in snarl_state.selected_nodes() { + if self.nodes.contains(node.0) { + ui.ctx().request_repaint(); + if snarl_state.selected_nodes().contains(&node) { + for node in snarl_state.selected_nodes() { + let node = &mut self.nodes[node.0]; + node.pos += delta; + } + } else { let node = &mut self.nodes[node.0]; node.pos += delta; } - } else { - let node = &mut self.nodes[node.0]; - node.pos += delta; } } - snarl_state.store(ui.ctx()); + snarl_state.store(self, ui.ctx()); }); } diff --git a/src/ui/state.rs b/src/ui/state.rs index ab1841c..bf8fa0f 100644 --- a/src/ui/state.rs +++ b/src/ui/state.rs @@ -1,4 +1,6 @@ -use egui::{ahash::HashSet, style::Spacing, Align, Context, Id, Pos2, Rect, Vec2}; +use std::hash::Hash; + +use egui::{ahash::HashSet, style::Spacing, Align, Context, Id, Pos2, Rect, Ui, Vec2}; use crate::{InPinId, NodeId, OutPinId, Snarl}; @@ -170,20 +172,100 @@ pub struct SnarlState { /// Active rect selection. rect_selection: Option, - /// Set of currently selected nodes. - selected_nodes: HashSet, + /// List of currently selected nodes. + selected_nodes: Vec, } #[derive(Clone)] +struct DrawOrder(Vec); + +#[derive(Clone)] +struct SelectedNodes(Vec); + struct SnarlStateData { offset: Vec2, scale: f32, target_scale: f32, - new_wires: Option, is_link_menu_open: bool, draw_order: Vec, + new_wires: Option, rect_selection: Option, - selected_nodes: HashSet, + selected_nodes: Vec, +} + +#[derive(Clone)] +struct SnarlStateDataHeader { + offset: Vec2, + scale: f32, + target_scale: f32, + is_link_menu_open: bool, +} + +impl SnarlStateData { + fn save(self, cx: &Context, id: Id) { + cx.data_mut(|d| { + d.insert_temp(id, SnarlStateDataHeader { + offset: self.offset, + scale: self.scale, + target_scale: self.target_scale, + is_link_menu_open: self.is_link_menu_open, + }); + + if let Some(new_wires) = self.new_wires { + d.insert_temp::(id, new_wires); + } else { + d.remove::(id); + } + + if let Some(rect_selection) = self.rect_selection { + d.insert_temp::(id, rect_selection); + } else { + d.remove::(id); + } + + if !self.selected_nodes.is_empty() { + d.insert_temp::(id, SelectedNodes(self.selected_nodes)); + } else { + d.remove::(id); + } + + if !self.draw_order.is_empty() { + d.insert_temp::(id, DrawOrder(self.draw_order)); + } else { + d.remove::(id); + } + }); + } + + fn load(cx: &Context, id: Id) -> Option { + cx.data(|d| { + let Some(small) = d.get_temp::(id) else { + return None; + }; + let new_wires = d.get_temp(id); + let rect_selection = d.get_temp(id); + + let selected_nodes = d.get_temp(id).unwrap_or(SelectedNodes(Vec::new())).0; + let draw_order = d.get_temp(id).unwrap_or(DrawOrder(Vec::new())).0; + + Some(SnarlStateData { + offset: small.offset, + scale: small.scale, + target_scale: small.target_scale, + is_link_menu_open: small.is_link_menu_open, + new_wires, + rect_selection, + selected_nodes, + draw_order, + }) + }) + } +} + +fn prune_selected_nodes(selected_nodes: &mut Vec, snarl: &Snarl) -> bool { + let old_size = selected_nodes.len(); + selected_nodes.retain(|node| snarl.nodes.contains(node.0)); + old_size != selected_nodes.len() } impl SnarlState { @@ -195,7 +277,7 @@ impl SnarlState { snarl: &Snarl, style: &SnarlStyle, ) -> Self { - let Some(mut data) = cx.data_mut(|d| d.get_temp::(id)) else { + let Some(mut data) = SnarlStateData::load(cx, id) else { return Self::initial(id, viewport, snarl, style); }; @@ -210,6 +292,8 @@ impl SnarlState { dirty = true; } + dirty |= prune_selected_nodes(&mut data.selected_nodes, snarl); + SnarlState { offset: data.offset, scale: data.scale, @@ -259,28 +343,26 @@ impl SnarlState { dirty: true, draw_order: Vec::new(), rect_selection: None, - selected_nodes: HashSet::default(), + selected_nodes: Vec::new(), } } #[inline(always)] - pub fn store(self, cx: &Context) { + pub fn store(mut self, snarl: &Snarl, cx: &Context) { + self.dirty |= prune_selected_nodes(&mut self.selected_nodes, snarl); + if self.dirty { - cx.data_mut(|d| { - d.insert_temp( - self.id, - SnarlStateData { - offset: self.offset, - scale: self.scale, - target_scale: self.target_scale, - new_wires: self.new_wires, - is_link_menu_open: self.is_link_menu_open, - draw_order: self.draw_order, - rect_selection: self.rect_selection, - selected_nodes: self.selected_nodes, - }, - ) - }); + let data = SnarlStateData { + offset: self.offset, + scale: self.scale, + target_scale: self.target_scale, + new_wires: self.new_wires, + is_link_menu_open: self.is_link_menu_open, + draw_order: self.draw_order, + rect_selection: self.rect_selection, + selected_nodes: self.selected_nodes, + }; + data.save(cx, self.id); } } @@ -475,34 +557,54 @@ impl SnarlState { self.dirty = true; } - pub fn selected_nodes(&self) -> &HashSet { + pub fn selected_nodes(&self) -> &[NodeId] { &self.selected_nodes } pub fn select_one_node(&mut self, reset: bool, node: NodeId) { if reset { + if self.selected_nodes[..] == [node] { + return; + } + self.deselect_all_nodes(); + self.selected_nodes.push(node); + self.dirty = true; + } else { + if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) { + if pos == self.selected_nodes.len() - 1 { + return; + } + self.selected_nodes.remove(pos); + } + self.selected_nodes.push(node); + self.dirty = true; } - - self.dirty |= self.selected_nodes.insert(node); } pub fn select_many_nodes(&mut self, reset: bool, nodes: impl Iterator) { if reset { self.deselect_all_nodes(); + self.selected_nodes.extend(nodes); + self.dirty = true; + } else { + nodes.for_each(|node| self.select_one_node(false, node)); } - - self.selected_nodes.extend(nodes); - self.dirty |= !self.selected_nodes.is_empty(); } pub fn deselect_one_node(&mut self, node: NodeId) { - self.dirty |= self.selected_nodes.remove(&node); + if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) { + self.selected_nodes.remove(pos); + self.dirty = true; + } } pub fn deselect_many_nodes(&mut self, nodes: impl Iterator) { for node in nodes { - self.dirty |= self.selected_nodes.remove(&node); + if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) { + self.selected_nodes.remove(pos); + self.dirty = true; + } } } @@ -540,3 +642,26 @@ impl SnarlState { Some(Rect::from_two_pos(rect.origin, rect.current)) } } + +impl Snarl { + /// Returns nodes selected in the UI. + /// + /// Use `id_source` and [`Ui`] that were used in [`Snarl::show`] method. + /// + /// If same [`Ui`] is not available, use [`Snarl::get_selected_nodes_at`] and provide `id` of the [`Ui`] used in [`Snarl::show`] method. + pub fn get_selected_nodes(id_source: impl Hash, ui: &mut Ui) -> Vec { + Self::get_selected_nodes_at(id_source, ui.id(), ui.ctx()) + } + + /// Returns nodes selected in the UI. + /// + /// Use `id_source` as well as [`Id`] and [`Context`] of the [`Ui`] that were used in [`Snarl::show`] method. + pub fn get_selected_nodes_at(id_source: impl Hash, id: Id, cx: &Context) -> Vec { + let snarl_id = id.with(id_source); + + cx.data(|d| { + d.get_temp::(snarl_id) + .unwrap_or(SelectedNodes(Vec::new())).0 + }) + } +}