Files
OneCAD-Draft/src/scene/view/viewport_pane.rs
T
Hakan SevenandClaude Opus 4.8 495b13ec4f 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>
2026-06-27 11:50:11 +03:00

114 lines
3.8 KiB
Rust

//! Unified GPU shader widget for both model and paper layouts.
//!
//! There is one `shader::Program` for the whole canvas. `Scene::build_viewports`
//! decides what to render — model layout produces one viewport per tile
//! (`active_viewports` iterates `model_tiles`), paper layout produces one per
//! content viewport entity. Each one is drawn into its own scissor rect by a
//! dedicated inner `Pipeline` (kept in the `MultiPipeline` outer).
use super::render::{CameraState, Primitive};
use crate::scene::Scene;
use iced::widget::shader;
use iced::{mouse, Event, Rectangle};
// ── Widget struct ─────────────────────────────────────────────────────────
pub struct ViewportPane<'a> {
pub scene: &'a Scene,
pub show_viewcube: bool,
/// Render mode applied to the Model layout's tiles. Paper-space content
/// viewports use the render mode stored on their own viewport entity.
pub render_mode: acadrust::entities::ViewportRenderMode,
/// `Some(tile_idx)` → this widget renders a single Model pane (one shader
/// per `pane_grid` pane, filling its own bounds). `None` → the unified
/// full-canvas path (paper layout, or the whole-canvas Model fallback).
pub pane: Option<usize>,
}
impl<'a> ViewportPane<'a> {
pub fn model(
scene: &'a Scene,
show_viewcube: bool,
render_mode: acadrust::entities::ViewportRenderMode,
) -> Self {
Self {
scene,
show_viewcube,
render_mode,
pane: None,
}
}
/// One Model `pane_grid` pane: renders just `tile_idx` into this widget's
/// own bounds (the pane rectangle).
pub fn for_pane(
scene: &'a Scene,
show_viewcube: bool,
render_mode: acadrust::entities::ViewportRenderMode,
tile_idx: usize,
) -> Self {
Self {
scene,
show_viewcube,
render_mode,
pane: Some(tile_idx),
}
}
}
// ── shader::Program impl ──────────────────────────────────────────────────
impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a> {
type State = CameraState;
type Primitive = Primitive;
fn draw(
&self,
state: &Self::State,
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
match self.pane {
Some(idx) => self
.scene
.build_viewport_for_pane(bounds, idx, self.render_mode),
None => self
.scene
.build_viewports(bounds, self.render_mode, state.hover_region),
}
}
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Option<iced::widget::Action<Msg>> {
// ViewCube hover is also driven from the app-level CursorMoved /
// ViewportMove handlers (the cube hit-area overlay shadows this
// widget for those events). Keeping the call here gives a fallback
// path while the cursor is over the bare shader.
if self.show_viewcube {
self.scene.update_viewcube_state(state, bounds, cursor);
} else {
state.hover_region = None;
}
let _ = event;
None
}
fn mouse_interaction(
&self,
state: &Self::State,
_b: Rectangle,
_c: mouse::Cursor,
) -> mouse::Interaction {
if self.show_viewcube {
self.scene.viewcube_mouse_interaction(state)
} else {
mouse::Interaction::default()
}
}
}