Docking: hide 'Move to group' when no panel accepts the move (lightcraft#1768)

A movable panel's context menu always offered 'Move to group', even
when the layout held no other panel that accepts tabs — one open panel
showed an empty submenu. Layout gains move_targets() (the same filter
the menu applies: every other panel that accepts_tabs), the menu renders
only when it is non-empty, and a test pins the lone-panel and
protected-anchor cases.

(cherry picked from commit 671463c1af)
This commit is contained in:
NibrasAz7 authored and aiongg committed 2026-10-19 04:17:34 +00:00
1 parent 5ac5d26be1
commit c4b007706d
3 files changed
+25 -3

No files matched your search

+6
View File
@@ -287,6 +287,12 @@ impl<P: Clone + Eq + Hash> Layout<P> {
self.panels().contains(&panel)
}
/// The panels `panel` can join as a tab: every other panel whose permissions accept tabs.
/// A "Move to group" menu with an empty answer has nothing to offer and stays hidden.
pub fn move_targets(&self, panel: &P, permissions: impl Fn(&P) -> Permissions) -> Vec<&P> {
self.panels().into_iter().filter(|target| *target != panel && permissions(target).accepts_tabs).collect()
}
/// Capture a panel's nearest surviving group/split neighbor before hiding it.
pub fn location(&self, panel: &P) -> Result<Location<P>, DockError> {
self.validate()?;
+3 -3
View File
@@ -712,12 +712,12 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
ui.close();
}
}
if policy.movable {
if policy.movable && !self.layout.move_targets(panel, self.permissions).is_empty() {
let text = self.content.action_label(ui, &self.style.move_label, &self.style.font, self.style.text);
let response = ui.menu_button(text, |ui| {
egui::ScrollArea::vertical().max_height(360.0).show(ui, |ui| {
for target in self.layout.panels() {
if target != panel && (self.permissions)(target).accepts_tabs && self.menu_label(ui, target).clicked() {
for target in self.layout.move_targets(panel, self.permissions) {
if self.menu_label(ui, target).clicked() {
self.output.actions.push(Action::Move {
panel: panel.clone(),
anchor: target.clone(),
+16
View File
@@ -51,6 +51,22 @@ fn detach_redock_merge_and_collapse_empty_source_containers() {
assert_eq!(layout.panels().len(), 6);
}
#[test]
fn move_targets_skip_the_panel_itself_and_protected_anchors() {
let layout = sample();
let all = |_: &u32| Permissions::default();
let targets = layout.move_targets(&1, all);
assert_eq!(targets.len(), 5);
assert!(!targets.contains(&&1));
let guarded = |p: &u32| if *p == 3 { Permissions::PROTECTED } else { Permissions::default() };
assert!(!layout.move_targets(&1, guarded).contains(&&3));
// A lone panel has nowhere to go — its "Move to group" menu hides
// (storytold/lightcraft#1768).
let single = Layout { root: Some(tabs(&[1], 0)), floating: Vec::new() };
assert!(single.move_targets(&1, all).is_empty());
}
#[test]
fn invalid_moves_are_atomic_including_after_a_source_would_have_been_removed() {
let layout = sample();