diff --git a/src/app/update/dialog.rs b/src/app/update/dialog.rs index fc46feea1..10795046f 100644 --- a/src/app/update/dialog.rs +++ b/src/app/update/dialog.rs @@ -644,7 +644,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven }; let index = crate::ui::dock::drop_index( point.y, - std::cmp::max(self.dock.len(side), 1), + std::cmp::max(self.dock_visible_len(side), 1), avail, ); self.dock_drag_target = Some((side, index)); @@ -681,6 +681,33 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven } } + /// Whether `id` is currently rendered (not closed, not on the start screen / + /// clean-screen viewport). Mirrors the visibility filter the edge column + /// renderer applies before building each side's stack. + pub(crate) fn dock_panel_visible(&self, id: crate::ui::dock::PanelId) -> bool { + use crate::ui::dock::PanelId; + if self.tabs[self.active_tab].is_start || self.clean_screen { + return false; + } + match id { + PanelId::Properties => self.show_properties, + PanelId::BlockPalette => self.show_block_palette, + } + } + + /// Number of panels currently rendered on `side`. Closed panels keep their + /// stack slot but take no vertical space, so drag preview geometry must + /// size slots against only the panels that are actually visible. + pub(crate) fn dock_visible_len(&self, side: crate::app::config::DockSide) -> usize { + let ids: &[crate::ui::dock::PanelId] = match side { + crate::app::config::DockSide::Left => &self.dock.left, + crate::app::config::DockSide::Right => &self.dock.right, + }; + ids.iter() + .filter(|id| self.dock_panel_visible(**id)) + .count() + } + /// Start placing `name` through the INSERT command, skipping the name prompt. fn start_block_placement(&mut self, name: &str) { let i = self.active_tab; @@ -1121,6 +1148,89 @@ mod tests { ); } + #[test] + fn dock_visible_len_counts_only_rendered_panels() { + let mut app = fresh(); + app.dock = Default::default(); + app.dock.left = vec![ + crate::ui::dock::PanelId::BlockPalette, + crate::ui::dock::PanelId::Properties, + ]; + // Left: block palette hidden, properties shown -> 1 visible panel. + app.show_block_palette = false; + app.show_properties = true; + assert_eq!( + app.dock_visible_len(crate::app::config::DockSide::Left), + 1 + ); + // Reveal the block palette -> both count. + app.show_block_palette = true; + assert_eq!( + app.dock_visible_len(crate::app::config::DockSide::Left), + 2 + ); + // A hidden (closed) panel counts for nothing even when stacked. + app.show_block_palette = false; + assert_eq!( + app.dock_visible_len(crate::app::config::DockSide::Left), + 1 + ); + } + + #[test] + fn dock_drag_target_ignores_hidden_panels_on_the_side() { + // Reproduce the persisted layout that exposed a half-height ghost: two + // panels live in the left stack but the block palette is hidden + // (show_block_palette=false), so only Properties renders. Drag geometry + // must count only panels that are actually visible. + let mut app = fresh(); + app.dock = Default::default(); + app.dock.left = vec![ + crate::ui::dock::PanelId::BlockPalette, + crate::ui::dock::PanelId::Properties, + ]; + app.show_block_palette = false; + app.show_properties = true; + let i = app.active_tab; + app.tabs[i].scene.selection.borrow_mut().vp_size = (1600.0, 900.0); + app.win_size = (1600.0, 900.0).into(); + let id = crate::ui::dock::PanelId::Properties; + let _ = app.on_dock(crate::ui::dock::DockMsg::DockGrab(id)); + // Pointer near the bottom of the left edge: one visible panel means a + // single slot, so every y maps to index 0 (no top/bottom split). + let _ = + app.on_dock(crate::ui::dock::DockMsg::DragMove(iced::Point::new(100.0, 850.0))); + assert_eq!( + app.dock_drag_target, + Some((crate::app::config::DockSide::Left, 0)) + ); + } + + #[test] + fn dock_drag_target_counts_all_visible_panels() { + let mut app = fresh(); + app.dock = Default::default(); + app.dock.left = vec![ + crate::ui::dock::PanelId::BlockPalette, + crate::ui::dock::PanelId::Properties, + ]; + // Both panels shown: two real slots on the left edge. A pointer near + // the bottom maps to the append slot (index == 2). + app.show_block_palette = true; + app.show_properties = true; + let i = app.active_tab; + app.tabs[i].scene.selection.borrow_mut().vp_size = (1600.0, 900.0); + app.win_size = (1600.0, 900.0).into(); + let id = crate::ui::dock::PanelId::Properties; + let _ = app.on_dock(crate::ui::dock::DockMsg::DockGrab(id)); + let _ = + app.on_dock(crate::ui::dock::DockMsg::DragMove(iced::Point::new(100.0, 850.0))); + assert_eq!( + app.dock_drag_target, + Some((crate::app::config::DockSide::Left, 2)) + ); + } + #[test] fn blockpalette_width_reset() { let mut app = fresh(); diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index fec1d8a81..daab2d005 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1528,13 +1528,17 @@ impl OpenCADStudio { .unwrap_or(crate::app::config::DockSide::Right), ); // Dropping onto an edge joins that edge's stack, whose column is as - // wide as its widest panel (the dragged panel included). + // wide as its widest currently-shown panel (the dragged panel + // included). Hidden (closed) panels don't render, so they can't + // widen the column being previewed. let preview_width = { let mut widths: Vec = match side { - crate::app::config::DockSide::Left => self.dock.left.clone(), - crate::app::config::DockSide::Right => self.dock.right.clone(), + crate::app::config::DockSide::Left => &self.dock.left, + crate::app::config::DockSide::Right => &self.dock.right, } - .into_iter() + .iter() + .copied() + .filter(|&pid| self.dock_panel_visible(pid)) .map(|pid| self.dock.width(pid, self.win_size.0)) .collect(); widths.push(self.dock.width(id, self.win_size.0)); @@ -1555,13 +1559,17 @@ impl OpenCADStudio { } }); // The ghost is the dragged panel at its real size: its own saved - // width and its 1/N share of the edge (N = stack size after the - // drop), with a header naming which panel is being moved. + // width and its 1/N share of the edge (N = number of panels shown + // on the edge after the drop), with a header naming which panel is + // being moved. Only visibly-docked panels share the column height: + // a hidden (closed) panel keeps its stack slot but no screen space, + // so it must not split the preview. let was_here = self.dock.location(id).map(|(s, _)| s) == Some(side); + let visible = self.dock_visible_len(side); let final_count = if was_here { - std::cmp::max(self.dock.len(side), 1) + std::cmp::max(visible, 1) } else { - self.dock.len(side) + 1 + visible + 1 }; let edge_h = tab.scene.selection.borrow().vp_size.1; let slot_h = edge_h / final_count as f32; diff --git a/src/ui/dock.rs b/src/ui/dock.rs index 9bbc2cc21..79981533f 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -195,11 +195,6 @@ impl DockState { stack.insert(index, id); true } - - /// Number of panels currently docked on `side`. - pub fn len(&self, side: DockSide) -> usize { - self.stack(side).len() - } } /// Insertion index (0..=total) for dropping a panel whose pointer is at @@ -319,11 +314,4 @@ mod tests { assert_eq!(drop_index(10.0, 2, 900.0), 0); assert_eq!(drop_index(890.0, 2, 900.0), 2); } - - #[test] - fn len_reports_stack_size() { - let state = DockState::default(); - assert_eq!(state.len(DockSide::Left), 1); - assert_eq!(state.len(DockSide::Right), 1); - } } \ No newline at end of file