docking: window.panel.dock to a side never anchors on what moves

On the first panel of the last column, the command picked that panel as
its own anchor and failed with InvalidPlacement. The anchor is a panel of
the last column outside the moving unit; when the unit already is that
column, the command does nothing.
This commit is contained in:
aiongg committed 2026-10-20 15:09:34 +00:00
1 parent 37374d06c0
commit 4310fc72a1
3 files changed
+20 -5

No files matched your search

+8 -4
View File
@@ -399,7 +399,11 @@ impl<P: Clone + Eq + Hash> DockCommand<P> {
}
if let Some(side) = side {
let to = match last_column(layout, *side) {
Some(anchor) => Target::At { anchor, placement: Placement::Column { after: true } },
Some(column) => match column.into_iter().find(|p| !members(layout, panel, unit).contains(p)) {
Some(anchor) => Target::At { anchor, placement: Placement::Column { after: true } },
// What moves already is that dock's last column.
None => return Ok(Vec::new()),
},
None => Target::NewDock { side: *side },
};
let unit = match (unit, &to) {
@@ -548,12 +552,12 @@ impl<P> DockCommand<P> {
}
}
/// The first panel of the last column of the dock on `side`.
fn last_column<P: Clone + Eq + Hash>(layout: &Layout<P>, side: Side) -> Option<P> {
/// The panels of the last column of the dock on `side`, or `None` without such a dock.
fn last_column<P: Clone + Eq + Hash>(layout: &Layout<P>, side: Side) -> Option<Vec<P>> {
let path = layout.dock_path(side)?;
let node = layout.root.as_ref().and_then(|root| super::dock_model::node_at(root, &path))?;
match node {
super::Node::Dock { columns, .. } => columns.last()?.groups.first()?.panels.first().cloned(),
super::Node::Dock { columns, .. } => Some(columns.last()?.groups.iter().flat_map(|g| g.panels.iter().cloned()).collect()),
_ => None,
}
}
+10
View File
@@ -409,3 +409,13 @@ fn dock_with_a_side_zone_and_no_anchor_makes_an_outer_edge_frame() {
);
rejected(json!({"command": "window.panel.dock", "panel": "color", "zone": "left", "before": null}), DockError::InvalidPlacement);
}
#[test]
fn dock_to_the_side_a_panel_already_leads_the_last_column_of() {
// Layers leads the dock's only column: it moves out to a new column after it.
let (workspace, _) = applied(json!({"command": "window.panel.dock", "panel": "layers", "side": "Start"}));
assert!(matches!(workspace.layout.place(&s("layers")), Some(dock_model::Place::Dock { column: 1, .. })));
assert!(matches!(workspace.layout.place(&s("channels")), Some(dock_model::Place::Dock { column: 0, .. })));
// The whole column already is the last one: nothing to do.
assert_eq!(actions(json!({"command": "window.panel.dock", "panel": "layers", "column": true, "side": "Start"})), vec![]);
}
+2 -1
View File
@@ -231,7 +231,8 @@ let closed = command.apply(&mut app.workspace, style.right_to_left, &space, |pan
`Placement::for_zone`, so beside a docked panel they make a new column or group. `apply` and
`to_actions` take the layout's `right_to_left` for that.
- `group` moves the panel's whole group; `column` moves its dock column, or its whole window when
it floats. `side` is `Start` or `End`.
it floats. `side` is `Start` or `End`: what moves becomes a new last column of that dock, or
a new dock; when it already is that dock's last column, the command does nothing.
- `window.panel.dock` with `left`, `right`, `top` or `bottom` and no `anchor` or `side` makes a
new frame on that outer edge (`Placement::Edge`). A hidden panel is shown first.
- `window.panel.float` without a rect floats what it moves at the size a drag floats it at (see