diff --git a/crates/ui/src/docking/dock_tests.rs b/crates/ui/src/docking/dock_tests.rs index 9a17ed7e..8f950481 100644 --- a/crates/ui/src/docking/dock_tests.rs +++ b/crates/ui/src/docking/dock_tests.rs @@ -481,3 +481,26 @@ fn right_to_left_mirrors_docks_their_edges_and_new_docks() { d.settle(); assert_eq!(d.rect("column-collapse", 5).left(), 0.0); } + +#[test] +fn a_column_s_first_tab_dropped_on_its_own_column_edge_anchors_on_another_panel() { + // Two columns, 0..240 and 240..480; the dragged tab is the first panel of the last one. + let wide = |columns: &[&[&[u32]]]| { + let mut layout = docked(Side::Start, columns); + if let Some(Node::Split { size, .. }) = &mut layout.root { + *size = SplitSize::FixedFirst(480.0); + } + layout + }; + let mut d = Driver::new(wide(&[&[&[2]], &[&[3, 4]]])); + let out = d.drag(d.tab(3).center(), pos2(484.0, 300.0)); + assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 4, placement: Placement::Column { after: true } }]); + assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2]], vec![vec![4]], vec![vec![3]]]); + + // Alone in its column, the panel stays where it is. + let layout = wide(&[&[&[2]], &[&[3]]]); + let mut d = Driver::new(layout.clone()); + let out = d.drag(d.tab(3).center(), pos2(484.0, 300.0)); + assert_eq!(out.actions, vec![]); + assert_eq!(d.layout, layout); +} diff --git a/crates/ui/src/docking/render/dock.rs b/crates/ui/src/docking/render/dock.rs index 3709d169..96936f7a 100644 --- a/crates/ui/src/docking/render/dock.rs +++ b/crates/ui/src/docking/render/dock.rs @@ -10,7 +10,8 @@ use egui::{Context, Key, Pos2, Rect, Sense, Stroke, StrokeKind, Ui, UiBuilder, p use super::{GroupKind, Renderer}; use crate::docking::{ - Action, Column, DockEvent, DockStyle, Group, GroupDrag, GroupGesture, Node, Profile, Side, Target, + Action, Column, DockEvent, DockStyle, Group, GroupDrag, GroupGesture, Node, Placement, Profile, Side, Target, + dock_model::Unit, targets::{DockDrop, DockTarget, column_edge, horizontal_line, resolve_dock, usable, vertical_line}, }; use crate::layout::{ResizeHandle, ResizeHandleStyle, SplitAxis}; @@ -471,6 +472,15 @@ impl Renderer<'_, P> { let candidates = self.dock_candidates(); let drop = resolve_dock(pointer, &candidates)?; let action = match drop.target.to() { + // A target named by the dragged panel is its own group or column: another panel + // there names the same one. With none, the drop leaves the panel where it is. + Target::At { anchor, placement } if &anchor == panel => { + let unit = if matches!(placement, Placement::Column { .. }) { Unit::Column } else { Unit::Group }; + match self.layout.members(panel, unit).ok()?.into_iter().find(|p| p != panel) { + Some(anchor) => Action::Move { panel: panel.clone(), anchor, placement }, + None => return Some((None, drop.line)), + } + } Target::At { anchor, placement } => Action::Move { panel: panel.clone(), anchor, placement }, to @ Target::NewDock { .. } if self.layout.place(panel).and_then(|place| self.layout.group(&place)).is_some_and(|g| g.panels.len() == 1) =>