graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: hide Move To Own Plate when no dock is free
Four tab candidates share three docks, so a dock can hold two tabs with
none left empty; the corner menu offered the split anyway and the row did
nothing. first_empty_dock is now the one answer both the menu and
split_dock_tab read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/app.rs | 22 +++++++++++++---------
src/main.rs | 40 ++++++++++++++++++++++++++++++++++++++++
src/plate_corner.rs | 4 +++-
3 files changed, 56 insertions(+), 10 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 248be24..4b7e8a4 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -3020,19 +3020,23 @@ impl State {
self.after_dock_change();
}
- /// Move the active tab of `slot`'s dock out to the first EMPTY dock —
- /// the corner menu's inverse of Add Tab. No empty dock, no move (with
- /// three panes on three docks, one is empty whenever any dock holds two).
+ /// The dock a split would move a tab to: the first EMPTY one, Left,
+ /// Right, Bottom. There are four tab candidates for three docks, so two
+ /// docks can each hold two with none left free — the corner menu reads
+ /// this to leave Move To Own Plate out rather than offer a dead row.
+ pub fn first_empty_dock(&self) -> Option<Dock> {
+ [Dock::Left, Dock::Right, Dock::Bottom]
+ .into_iter()
+ .find(|&d| self.dock_tabs[d as usize].is_empty())
+ }
+
+ /// Move `slot` out of its shared dock to the first EMPTY dock — the
+ /// corner menu's inverse of Add Tab. No empty dock, no move.
pub fn split_dock_tab(&mut self, slot: usize) {
if self.tab_dock_of_pane(slot).is_none() {
return;
}
- let Some(empty) = [Dock::Left, Dock::Right, Dock::Bottom]
- .into_iter()
- .find(|&d| self.dock_tabs[d as usize].is_empty())
- else {
- return;
- };
+ let Some(empty) = self.first_empty_dock() else { return };
self.add_dock_tab(empty, slot);
}
diff --git a/src/main.rs b/src/main.rs
index b3aeabc..f3968f1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1161,6 +1161,46 @@ mod tests {
assert_eq!(state.pane_in_dock(Dock::Right), SPREADSHEET_IDX);
}
+ /// Move To Own Plate is offered only while a dock is free to take the
+ /// pane. Four tab candidates share three docks, so a dock can hold two
+ /// with none empty — and there the row used to show and do nothing.
+ #[test]
+ fn move_to_own_plate_needs_an_empty_dock() {
+ use crate::app::Dock;
+ use crate::plate_corner::PlateMenuAction;
+ use crate::slots::{NETWORK_PANEL2_IDX, NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX};
+ let mut state = State::new(false);
+ state.resize(1600.0, 900.0, 1.0);
+
+ // The second editor tabs in beside the first: left holds two, and
+ // params and spreadsheet keep the other two docks, so none is free.
+ state.add_dock_tab(Dock::Left, NETWORK_PANEL2_IDX);
+ state.show_dock_tab(Dock::Left, NETWORK_PANEL_IDX);
+ assert_eq!(state.first_empty_dock(), None);
+ state.open_plate_menu(NETWORK_PANEL_IDX);
+ assert!(!state.plate_menu_actions.contains(&PlateMenuAction::SplitTab),
+ "no dock is free, so the row is left out");
+ assert!(state.plate_menu_actions.contains(&PlateMenuAction::ShowTab(NETWORK_PANEL2_IDX)),
+ "the tab list itself still shows");
+ state.close_plate_menu();
+
+ // Pulling the spreadsheet in beside the params frees the bottom dock,
+ // and the row comes back — on both shared docks.
+ state.add_dock_tab(Dock::Right, SPREADSHEET_IDX);
+ state.show_dock_tab(Dock::Right, PARAM_IDX);
+ assert_eq!(state.first_empty_dock(), Some(Dock::Bottom));
+ for idx in [NETWORK_PANEL_IDX, PARAM_IDX] {
+ state.open_plate_menu(idx);
+ assert!(state.plate_menu_actions.contains(&PlateMenuAction::SplitTab),
+ "a shared dock with a free one offers the split");
+ state.close_plate_menu();
+ }
+
+ // And the split lands there.
+ state.split_dock_tab(PARAM_IDX);
+ assert_eq!(state.pane_in_dock(Dock::Bottom), PARAM_IDX);
+ }
+
/// The second network editor: joins a dock from nowhere through the tab
/// machinery, dives on its OWN path while the primary stays put, clamps
/// a stale path instead of panicking, and Close removes it entirely.
diff --git a/src/plate_corner.rs b/src/plate_corner.rs
index 69535dc..00e8211 100644
--- a/src/plate_corner.rs
+++ b/src/plate_corner.rs
@@ -235,7 +235,9 @@ impl State {
options.push("Add Tab".to_string());
actions.push(PlateMenuAction::AddTabMenu);
}
- if self.dock_tabs[d as usize].len() > 1 {
+ // Only while a dock is free to take it: with none, the split
+ // has nowhere to go and the row would do nothing.
+ if self.dock_tabs[d as usize].len() > 1 && self.first_empty_dock().is_some() {
manage_row(&mut options, &mut actions);
options.push("Move To Own Plate".to_string());
actions.push(PlateMenuAction::SplitTab);