forked from 1b-agent/opencadstudio-upstream
Merge pull request #1702 from MarcelP102/Pallet-placement
[UX] Pallets you can dock, group and float (including LAYERS panel)
This commit is contained in:
commit
7ca457a6cc
54 files changed
+4245
-1098
No files matched your search
@@ -134,7 +134,7 @@ impl OpenCADStudio {
|
||||
/// BLOCKSPALETTE: open (expanded) on `tab`, or on the tab it was left on.
|
||||
pub(crate) fn open_blocks_palette(&mut self, tab: Option<Tab>) {
|
||||
self.show_block_palette = true;
|
||||
self.dock_expanded = Some(crate::ui::dock::PanelId::BlockPalette);
|
||||
self.dock_reveal(crate::ui::dock::PanelId::BlockPalette);
|
||||
self.show_block_palette_tab(tab.unwrap_or(self.block_palette.tab));
|
||||
}
|
||||
|
||||
|
||||
@@ -542,195 +542,6 @@ impl OpenCADStudio {
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
/// Dock chrome interaction (grab / pin / resize / hover / move) applied to
|
||||
/// whichever panel the message names.
|
||||
pub(super) fn on_dock(&mut self, m: crate::ui::dock::DockMsg) -> iced::Task<Message> {
|
||||
use crate::app::config::DockSide;
|
||||
use crate::ui::dock::{DockMsg, PanelId};
|
||||
match m {
|
||||
DockMsg::DockGrab(id) => {
|
||||
self.dock_dragging = Some(id);
|
||||
self.dock_resizing = None;
|
||||
self.dock_drag_last = None;
|
||||
self.dock_drag_target = self.dock.location(id);
|
||||
self.dock_expanded = Some(id);
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::ResizeGrab(id) => {
|
||||
self.dock_resizing = Some(id);
|
||||
self.dock_dragging = None;
|
||||
self.dock_drag_last = None;
|
||||
self.dock_drag_target = None;
|
||||
self.dock_expanded = Some(id);
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::WidthReset(id) => {
|
||||
self.dock.reset_width(id);
|
||||
self.save_config();
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::AutoCollapseToggle(id) => {
|
||||
let on = !self.dock.auto_collapse(id);
|
||||
self.dock.set_auto_collapse(id, on);
|
||||
self.dock_expanded = if on { None } else { Some(id) };
|
||||
self.save_config();
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::Close(id) => {
|
||||
match id {
|
||||
PanelId::BlockPalette => {
|
||||
self.show_block_palette = false;
|
||||
self.block_palette.placing = None;
|
||||
}
|
||||
PanelId::ExternalReferences => {
|
||||
self.show_external_references = false;
|
||||
}
|
||||
PanelId::Browser => {
|
||||
self.show_browser = false;
|
||||
}
|
||||
PanelId::NodeGraph => {
|
||||
self.show_node_graph = false;
|
||||
}
|
||||
PanelId::PointCloudManager => {
|
||||
self.pc_manager.show = false;
|
||||
}
|
||||
PanelId::Count => {
|
||||
self.count_palette.show = false;
|
||||
self.ribbon.set_count_palette(false);
|
||||
}
|
||||
PanelId::SheetSetManager => {
|
||||
self.sheet_set.show = false;
|
||||
self.ribbon.set_sheet_set(false);
|
||||
}
|
||||
PanelId::Properties => {
|
||||
self.show_properties = false;
|
||||
self.ribbon.set_properties(false);
|
||||
}
|
||||
}
|
||||
if self.dock_expanded == Some(id) {
|
||||
self.dock_expanded = None;
|
||||
}
|
||||
if self.dock_dragging == Some(id) {
|
||||
self.dock_dragging = None;
|
||||
}
|
||||
if self.dock_resizing == Some(id) {
|
||||
self.dock_resizing = None;
|
||||
}
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::Hover(id) => {
|
||||
// Ignored while dragging/resizing: the pointer is over the
|
||||
// drag preview, not a rail, so a hover must not collapse the
|
||||
// panel being dragged or repoint the resize target.
|
||||
if self.dock_dragging.is_none() && self.dock_resizing.is_none() {
|
||||
self.dock_expanded = Some(id);
|
||||
}
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::HoverExit => {
|
||||
if self.dock_dragging.is_none() && self.dock_resizing.is_none() {
|
||||
if let Some(id) = self.dock_expanded {
|
||||
if self.dock.auto_collapse(id) {
|
||||
self.dock_expanded = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::DragMove(point) => {
|
||||
// NOTE: xref column drags intentionally do NOT ride this
|
||||
// path: its points live in workspace space while the header
|
||||
// tracker reports header-local points, and mixing the two
|
||||
// produced a one-time jump plus a stuck drag. Column moves
|
||||
// arrive via Message::XrefColMove only.
|
||||
if self.dock_dragging.is_some() {
|
||||
let avail = self.tabs[self.active_tab]
|
||||
.scene
|
||||
.selection
|
||||
.borrow()
|
||||
.view
|
||||
.vp_size
|
||||
.1;
|
||||
let side = if point.x < self.win_size.0 * 0.5 {
|
||||
DockSide::Left
|
||||
} else {
|
||||
DockSide::Right
|
||||
};
|
||||
let index = crate::ui::dock::drop_index(
|
||||
point.y,
|
||||
std::cmp::max(self.dock_visible_len(side), 1),
|
||||
avail,
|
||||
);
|
||||
self.dock_drag_target = Some((side, index));
|
||||
} else if let Some(id) = self.dock_resizing {
|
||||
if let Some(last) = self.dock_drag_last {
|
||||
let dx = point.x - last.x;
|
||||
let delta = match self.dock.location(id) {
|
||||
Some((DockSide::Left, _)) => dx,
|
||||
_ => -dx,
|
||||
};
|
||||
let cur = self.dock.settings(id).width + delta;
|
||||
self.dock.set_width(id, cur);
|
||||
}
|
||||
}
|
||||
if self.dock_dragging.is_some() || self.dock_resizing.is_some() {
|
||||
self.dock_drag_last = Some(point);
|
||||
}
|
||||
iced::Task::none()
|
||||
}
|
||||
DockMsg::DragRelease => {
|
||||
if let Some(id) = self.dock_dragging {
|
||||
if let Some((side, index)) = self.dock_drag_target {
|
||||
if self.dock.dock(id, side, index) {
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dock_dragging = None;
|
||||
self.dock_resizing = None;
|
||||
self.dock_drag_last = None;
|
||||
self.dock_drag_target = None;
|
||||
self.xref_col_drag = None;
|
||||
self.xref_col_last = None;
|
||||
self.xref_split_drag = false;
|
||||
iced::Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
PanelId::ExternalReferences => self.show_external_references,
|
||||
PanelId::Browser => self.show_browser,
|
||||
PanelId::NodeGraph => self.show_node_graph,
|
||||
PanelId::PointCloudManager => self.pc_manager.show,
|
||||
PanelId::Count => self.count_palette.show,
|
||||
PanelId::SheetSetManager => self.sheet_set.show,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Rebuild the Reference Manager's entry list from the active drawing.
|
||||
/// Uses the tab's session sets (unloaded + stat baseline) so CLI and
|
||||
/// palette agree; writes fresh load-time mtimes back into the tab's stat
|
||||
@@ -1605,134 +1416,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockpalette_pin_toggles_autocollapse_and_close_hides() {
|
||||
let mut app = fresh();
|
||||
app.show_block_palette = true;
|
||||
let id = crate::ui::dock::PanelId::BlockPalette;
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::AutoCollapseToggle(id));
|
||||
assert!(app.dock.auto_collapse(id), "pin enables auto-collapse");
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::AutoCollapseToggle(id));
|
||||
assert!(
|
||||
!app.dock.auto_collapse(id),
|
||||
"second pin disables auto-collapse"
|
||||
);
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::Close(id));
|
||||
assert!(!app.show_block_palette, "close dismisses the sidebar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockpalette_dock_moves_to_other_side_and_persists() {
|
||||
let mut app = fresh();
|
||||
// Tests load the user's persisted config; reset the dock to a known
|
||||
// state so this stays hermetic.
|
||||
app.dock = Default::default();
|
||||
let id = crate::ui::dock::PanelId::BlockPalette;
|
||||
assert_eq!(
|
||||
app.dock.location(id),
|
||||
Some((crate::app::config::DockSide::Right, 0))
|
||||
);
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::DockGrab(id));
|
||||
app.win_size = (1600.0, 900.0).into();
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::DragMove(iced::Point::new(
|
||||
100.0, 100.0,
|
||||
)));
|
||||
assert_eq!(
|
||||
app.dock_drag_target,
|
||||
Some((crate::app::config::DockSide::Left, 0))
|
||||
);
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::DragRelease);
|
||||
assert_eq!(
|
||||
app.dock.location(id),
|
||||
Some((crate::app::config::DockSide::Left, 0))
|
||||
);
|
||||
}
|
||||
|
||||
#[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().view.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().view.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();
|
||||
let id = crate::ui::dock::PanelId::BlockPalette;
|
||||
app.dock.set_width(id, 500.0);
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::WidthReset(id));
|
||||
assert_eq!(app.dock.settings(id).width, 260.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_name_from_file_avoids_collisions() {
|
||||
let mut app = fresh();
|
||||
|
||||
+17
-24
@@ -2608,27 +2608,19 @@ impl OpenCADStudio {
|
||||
Message::ScriptLine(line) => self.feed_script_line(&line),
|
||||
|
||||
Message::ToggleLayers => {
|
||||
if self.active_modal == Some(super::ModalKind::Layers) {
|
||||
self.ribbon.deactivate_tool_if("LAYERS");
|
||||
self.active_modal = None;
|
||||
self.reset_modal_geometry();
|
||||
} else {
|
||||
self.sync_ribbon_layers();
|
||||
self.active_modal = Some(super::ModalKind::Layers);
|
||||
if self.show_layers {
|
||||
return self.on_dock(crate::ui::dock::DockMsg::Close(
|
||||
crate::ui::dock::PanelId::Layers,
|
||||
));
|
||||
}
|
||||
self.open_layers_pallet();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ToggleXrefManager => {
|
||||
self.show_external_references ^= true;
|
||||
if self.show_external_references {
|
||||
use crate::app::config::DockSide;
|
||||
use crate::ui::dock::PanelId;
|
||||
if self.dock.location(PanelId::ExternalReferences).is_none() {
|
||||
self.dock
|
||||
.dock(PanelId::ExternalReferences, DockSide::Right, usize::MAX);
|
||||
}
|
||||
self.dock_expanded = Some(PanelId::ExternalReferences);
|
||||
self.dock_open_at_default(crate::ui::dock::PanelId::ExternalReferences);
|
||||
self.refresh_xref_manager();
|
||||
}
|
||||
Task::none()
|
||||
@@ -6362,8 +6354,11 @@ impl OpenCADStudio {
|
||||
// Ctrl+A in the MText editor selects all of its text.
|
||||
self.mtext_select_all();
|
||||
Task::none()
|
||||
} else if self.active_modal == Some(super::ModalKind::Layers) {
|
||||
// Select every row in the Layer Manager (#236).
|
||||
} else if self.dock_panel_visible(crate::ui::dock::PanelId::Layers)
|
||||
&& self.tabs[i].layers.selected.is_some()
|
||||
{
|
||||
// Select every row in the Layer Manager (#236) while the
|
||||
// user is working in it (a layer row is selected).
|
||||
let n = self.tabs[i].layers.layers.len();
|
||||
self.tabs[i].layers.selected_multi = (0..n).collect();
|
||||
self.tabs[i].layers.selected = (n > 0).then_some(0);
|
||||
@@ -9144,17 +9139,16 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
Message::LayerNameColGrab => {
|
||||
// Start a Name-column divider drag; rides ModalDragMove.
|
||||
self.layer_col_dragging = true;
|
||||
self.modal_drag_last = None;
|
||||
// Start a Name-column divider drag; the dock's pointer capture
|
||||
// feeds it (see `on_dock`).
|
||||
self.dock_drag = Some(crate::ui::dock::DockDrag::LayerColumn);
|
||||
self.dock_drag_last = None;
|
||||
Task::none()
|
||||
}
|
||||
Message::ModalDragMove(p) => {
|
||||
if let Some(last) = self.modal_drag_last {
|
||||
let (dx, dy) = (p.x - last.x, p.y - last.y);
|
||||
if self.layer_col_dragging {
|
||||
self.layer_name_col_w = (self.layer_name_col_w + dx).clamp(60.0, 640.0);
|
||||
} else if self.modal_resizing {
|
||||
if self.modal_resizing {
|
||||
// The grip sits bottom-right, so dragging out grows the
|
||||
// box. The delta is added to each dialog's natural size,
|
||||
// so clamp it at zero — dragging in past the natural size
|
||||
@@ -9188,7 +9182,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.modal_dragging || self.modal_resizing || self.layer_col_dragging {
|
||||
if self.modal_dragging || self.modal_resizing {
|
||||
self.modal_drag_last = Some(p);
|
||||
}
|
||||
Task::none()
|
||||
@@ -9196,7 +9190,6 @@ impl OpenCADStudio {
|
||||
Message::ModalDragRelease => {
|
||||
self.modal_dragging = false;
|
||||
self.modal_resizing = false;
|
||||
self.layer_col_dragging = false;
|
||||
self.modal_drag_last = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user