refactor(viewport): migrate model-space tiling to iced pane_grid
Replace the custom model_tiles tiling with iced 0.14 pane_grid as the layout source of truth, while paper-space floating viewports are left untouched (they overlap/free-place, which pane_grid cannot model). Each Model pane is its own shader widget rendering its camera into the pane bounds; the renderer Primitive carries a base_slot so several shader widgets share the type-keyed MultiPipeline storage on disjoint slots. A second pane_grid of transparent mouse_areas (plus on_resize) layers above the crosshair overlay to receive input — the overlay's Hidden cursor interaction otherwise levitates the cursor and starves any layer beneath it. The active pane follows the cursor (hover-activate), and grid / crosshair / UCS icon render per pane while the ViewCube and render-mode picker stay on the active pane only. Split / close / VPORTS presets and the VPort table round-trip now drive pane_grid (guillotine reconstruction on load). Adds drag-to-swap via a controls bar handle with a ghost-card preview, visible pane dividers from the pane_grid split regions, a default model background of RGB(33,40,48), and removes all the superseded custom-tile code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8bdf5228d2
commit
495b13ec4f
18 changed files with 1004 additions and 733 deletions
|
|
@ -1,7 +1,5 @@
|
|||
use super::{Message, OpenCADStudio};
|
||||
use crate::scene::{
|
||||
VIEWCUBE_DRAW_PX, VIEWCUBE_PAD,
|
||||
};
|
||||
use crate::scene::VIEWCUBE_DRAW_PX;
|
||||
use crate::ui::PropertiesPanel;
|
||||
use acadrust::types::Color as AcadColor;
|
||||
use iced::time::Instant;
|
||||
|
|
@ -42,19 +40,6 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
|
|||
}
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX;
|
||||
/// Pixel distance from a Model-tile inner divider that still registers as
|
||||
/// a resize grip on the press.
|
||||
const TILE_EDGE_HIT_PX: f32 = 4.0;
|
||||
/// Normalized minimum tile size a divider drag clamps to. Sized to
|
||||
/// comfortably contain the ViewCube + its padding so every tile always
|
||||
/// has room to show the gizmo.
|
||||
fn tile_min_norm(canvas_w: f32, canvas_h: f32) -> (f32, f32) {
|
||||
let px = VIEWCUBE_DRAW_PX + 2.0 * VIEWCUBE_PAD + 16.0;
|
||||
(
|
||||
(px / canvas_w.max(1.0)).min(0.95),
|
||||
(px / canvas_h.max(1.0)).min(0.95),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
const KB: f64 = 1024.0;
|
||||
|
|
@ -706,15 +691,17 @@ impl OpenCADStudio {
|
|||
|
||||
Message::SplitModelViewport(horizontal) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.split_active_model_tile(horizontal);
|
||||
self.tabs[i].scene.split_active_pane(horizontal);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CloseModelViewport => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.close_active_model_tile();
|
||||
self.tabs[i].scene.close_active_pane();
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
self.adopt_view_display(i);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
@ -1021,6 +1008,69 @@ impl OpenCADStudio {
|
|||
|
||||
Message::ViewportExit => self.on_viewport_exit(),
|
||||
|
||||
// ── Per-pane Model viewport ───────────────────────────────────
|
||||
Message::PaneResized(ev) => self.on_pane_resized(ev),
|
||||
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
|
||||
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
|
||||
Message::PaneMove(idx, local) => {
|
||||
let p = self.pane_canvas_point(idx, local);
|
||||
// While dragging a pane, just track the cursor (no focus swap or
|
||||
// snap) so the drop target reads cleanly.
|
||||
if self.pane_move_from.is_some() {
|
||||
self.tabs[self.active_tab]
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.last_move_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_move(p)
|
||||
}
|
||||
Message::PaneMoveStart => {
|
||||
let i = self.active_tab;
|
||||
self.pane_move_from = Some(self.tabs[i].scene.active_model_tile.get());
|
||||
Task::none()
|
||||
}
|
||||
Message::PanePress(idx) => {
|
||||
// A fresh press ends any stale (un-dropped) pane move.
|
||||
self.pane_move_from = None;
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_left_press()
|
||||
}
|
||||
Message::PaneRelease(idx) => {
|
||||
// Finishing a pane-move drag: swap the source pane with the one
|
||||
// released over, instead of the normal release handling.
|
||||
if let Some(from) = self.pane_move_from.take() {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.swap_model_panes(from, idx);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
return Task::none();
|
||||
}
|
||||
self.focus_model_pane(idx);
|
||||
self.on_viewport_left_release()
|
||||
}
|
||||
Message::PaneRightPress(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportRightPress)
|
||||
}
|
||||
Message::PaneRightRelease(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportRightRelease)
|
||||
}
|
||||
Message::PaneMiddlePress(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportMiddlePress)
|
||||
}
|
||||
Message::PaneMiddleRelease(idx) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportMiddleRelease)
|
||||
}
|
||||
Message::PaneScroll(idx, d) => {
|
||||
self.focus_model_pane(idx);
|
||||
self.update(Message::ViewportScroll(d))
|
||||
}
|
||||
|
||||
Message::ViewportLeftPress => self.on_viewport_left_press(),
|
||||
|
||||
Message::ViewportLeftRelease => self.on_viewport_left_release(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue