From d6a381e8a9027c2e05671a39598fd5d68621edfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kn=C3=B6pfle?= Date: Sun, 4 Jan 2026 19:16:15 +0100 Subject: [PATCH] Improve drag UI --- client/src/components/mindmap.rs | 34 +++++++++++++----------------- client/src/components/mod.rs | 1 - client/src/components/node.rs | 12 ++--------- client/src/components/node_link.rs | 17 +++++++++------ client/src/data/graph.rs | 28 +++++++++++++++++++++--- client/src/data/pane.rs | 11 ++++++++++ 6 files changed, 64 insertions(+), 39 deletions(-) diff --git a/client/src/components/mindmap.rs b/client/src/components/mindmap.rs index 514dbad..e819a25 100644 --- a/client/src/components/mindmap.rs +++ b/client/src/components/mindmap.rs @@ -1,4 +1,3 @@ -use crate::components::DraggedNode; use crate::components::LocationIndicator; use crate::components::MiniMap; use crate::components::Node; @@ -29,13 +28,18 @@ pub fn Mindmap() -> Element { if let Some(parent_id) = node.parent_id { NodeLink { id: node.id, parent_id, store: store.clone() } } + Node { id: node.id, store: store.clone() } }, )); }); nodes.sort_by_key(|(id, _)| { let root_id = graph.get_root(*id); - (dragging_id != Some(root_id), root_id) + ( + dragging_id == Some(*id), + dragging_id != Some(root_id), + root_id, + ) }); rsx! { div { @@ -140,7 +144,7 @@ pub fn Mindmap() -> Element { if let Some(dragging_node) = dragging_node { let target = graph.on_other(dragging_node.id, svg_coords); pane.update_drag(svg_coords, target); - graph.move_node(dragging_node.id, dragging_node.coords); + graph.move_root_node(dragging_node.id, dragging_node.coords); } if *pane.panning.read() { let (start_x, start_y) = *pane.pan_offset.read(); @@ -190,22 +194,14 @@ pub fn Mindmap() -> Element { if let Some(dragging_node) = *pane.dragging_node.read() { if dragging_node.has_moved { - if let Some(node) = graph.get_node(dragging_node.id) { - if node.parent_id.is_some() { - DraggedNode { - id: node.id, - coords: dragging_node.coords, - } - } - if let Some((_, location)) = dragging_node.target { - g { - transform: format!( - "translate({},{})", - dragging_node.coords.0 + 8.0, - dragging_node.coords.1 - 8.0, - ), - LocationIndicator { location } - } + if let Some((_, location)) = dragging_node.target { + g { + transform: format!( + "translate({},{})", + dragging_node.coords.0 + 8.0, + dragging_node.coords.1 - 8.0, + ), + LocationIndicator { location } } } } diff --git a/client/src/components/mod.rs b/client/src/components/mod.rs index 6e7972a..3bf5ffb 100644 --- a/client/src/components/mod.rs +++ b/client/src/components/mod.rs @@ -3,7 +3,6 @@ //! component and an Echo component for fullstack apps to be used in our app. mod node; -pub use node::DraggedNode; pub use node::Node; mod node_link; diff --git a/client/src/components/node.rs b/client/src/components/node.rs index 529f210..e695260 100644 --- a/client/src/components/node.rs +++ b/client/src/components/node.rs @@ -6,15 +6,6 @@ use uuid::Uuid; const SELECTED_PADDING: f32 = 5.0; -#[component] -pub fn DraggedNode(id: Uuid, coords: (f32, f32)) -> Element { - rsx! { - g { transform: format!("translate({}, {})", coords.0, coords.1), - r#use { href: format!("#{id}") } - } - } -} - #[component] fn RawChildNode(width: f32, height: f32, color: String) -> Element { rsx! { @@ -153,6 +144,7 @@ pub fn Node(id: Uuid, store: Store) -> Element { let height = node.height(); let font_size = node.font_size(); let is_editing = *store.pane.editing.read() == Some(id); + let (node_x, node_y) = store.pane.coords(&node); let mut input_element: Signal>> = use_signal(|| None); use_effect(move || { if let Some(input) = &*input_element.read() { @@ -161,7 +153,7 @@ pub fn Node(id: Uuid, store: Store) -> Element { }); rsx! { - g { transform: format!("translate({},{})", node.x, node.y), + g { transform: format!("translate({},{})", node_x, node_y), g { onmousedown: move |evt| { if is_editing { diff --git a/client/src/components/node_link.rs b/client/src/components/node_link.rs index e4080ff..054002e 100644 --- a/client/src/components/node_link.rs +++ b/client/src/components/node_link.rs @@ -36,26 +36,31 @@ pub fn RedCross(x: f32, y: f32) -> Element { #[component] pub fn NodeLink(id: Uuid, parent_id: Uuid, store: Store) -> Element { let graph = store.graph; + let Some(child) = graph.get_node(id) else { return rsx! {}; }; + let (child_x, child_y) = store.pane.coords(&child); + let Some(parent) = graph.get_node(parent_id) else { - let x = child.x - child.width() / 2.0; - let y = child.y; + let x = child_x - child.width() / 2.0; + let y = child_y; return rsx! { RedCross { x, y } }; }; + let (parent_x, parent_y) = store.pane.coords(&parent); + let is_right = child.x > parent.x; let side_mult = if is_right { 1.0 } else { -1.0 }; // Parent/child edges - let start_x = parent.x + side_mult * parent.width() / 2.0; - let start_y = parent.y; - let end_x = child.x - side_mult * child.width() / 2.0; - let end_y = child.y; + let start_x = parent_x + side_mult * parent.width() / 2.0; + let start_y = parent_y; + let end_x = child_x - side_mult * child.width() / 2.0; + let end_y = child_y; // Horizontal offsets let start_offset = 20.0 * side_mult; diff --git a/client/src/data/graph.rs b/client/src/data/graph.rs index 8be08ac..a782d2b 100644 --- a/client/src/data/graph.rs +++ b/client/src/data/graph.rs @@ -225,6 +225,14 @@ impl Graph { } } + pub fn move_root_node(&mut self, id: Uuid, coords: (f32, f32)) { + if let Some(node) = self.get_node(id) { + if node.parent_id.is_none() { + self.doc.write().update_node_coords(id, coords); + } + } + } + pub fn move_node_into( &mut self, id: Uuid, @@ -232,9 +240,7 @@ impl Graph { target: Option<(Uuid, RelativeLocation)>, ) { if let Some((target_id, location)) = target { - if self.get_root(target_id) == id { - self.move_node(id, coords); - } else { + if !self.ancestors(target_id).contains(&id) { let side = match location { RelativeLocation::Left => Side::Left, RelativeLocation::Right => Side::Right, @@ -265,6 +271,22 @@ impl Graph { .unwrap_or(id) } + pub fn ancestors(&self, id: Uuid) -> Vec { + let mut result = match self.get_node(id) { + Some(node) => { + if let Some(parent_id) = node.parent_id { + self.ancestors(parent_id) + } else { + Vec::new() + } + } + None => Vec::new(), + }; + + result.push(id); + result + } + pub fn on(&self, coords: (f32, f32)) -> Option<(Uuid, RelativeLocation)> { let mut target = None; for node in self.nodes.read().values() { diff --git a/client/src/data/pane.rs b/client/src/data/pane.rs index 2bc4015..c0dcf23 100644 --- a/client/src/data/pane.rs +++ b/client/src/data/pane.rs @@ -1,3 +1,5 @@ +use std::f32; + use dioxus::prelude::*; use uuid::Uuid; @@ -55,6 +57,15 @@ impl Pane { (x - t.pan_x, y - t.pan_y) } + pub fn coords(&self, node: &RenderedNode) -> (f32, f32) { + if let Some(dragging_node) = *self.dragging_node.read() { + if dragging_node.id == node.id { + return dragging_node.coords; + } + } + (node.x, node.y) + } + pub fn start_drag(&mut self, node: &RenderedNode, (x, y): (f32, f32)) { let ox = x - node.x; let oy = y - node.y;