Both items were already implemented prior to this commit: - MSPACE mouse routing (double-click/pan/zoom/Escape) was fully wired through the existing viewport_mouse overlay and scene helpers. - Per-viewport frozen layer filtering uses matching Handle values from the same CadDocument, confirmed correct in model_wires_for_viewport(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.7 KiB
Unified ViewportPane Widget Plan
Current State (as of implementation start)
src/scene/render.rs:Sceneimplementsshader::Program<Msg>→ single full-screen GPU widgetsrc/app/view.rs:106:shader(&tab.scene).width(Fill).height(Fill)— same widget for both model and paper space, only render content differssrc/entities/viewport.rs:Viewportentity is a pure DXF data struct with no shader or widget of its own
Target Architecture
Model tab → ViewportPane (mode: Model) — Fill × Fill
Paper tab → paper_canvas_view()
├── ViewportPane (mode: PaperSheet) — Fill × Fill (paper entities + camera)
├── ViewportPane (mode: Paper, VP1) — vp.width × vp.height px
└── ViewportPane (mode: Paper, VP2) — vp.width × vp.height px
✅ Step 1 — ViewportPane Struct
File: src/scene/viewport_pane.rs
pub enum ViewportPaneMode {
Model,
PaperSheet, // paper-space entities, paper camera, no viewport projection
Paper { handle: Handle }, // model content through a specific viewport's camera
}
pub struct ViewportPane<'a> { pub scene: &'a Scene, pub mode: ViewportPaneMode }
Implements shader::Program<Msg>. Scene's own shader::Program impl has been removed — all rendering now goes through ViewportPane.
Scene helper methods added to render.rs:
build_primitive()— model/full paper space render (wasdraw())build_paper_sheet_primitive()— paper entities only, no viewport projectionbuild_viewport_primitive(vp_handle)— model content through viewport cameraupdate_viewcube_state()/viewcube_mouse_interaction()
Scene helper methods added to mod.rs:
paper_sheet_wires()— paper-space entity wires without viewport contentcamera_for_viewport(handle)— build Camera from Viewport entity datamodel_wires_for_viewport(handle)— model wires filtered by viewport layer freeze
✅ Step 2 — Per-Viewport Camera
Implemented via camera_for_viewport() in src/scene/mod.rs. Derives a Camera from the Viewport entity's view_direction, view_target, and view_height each frame — no separate HashMap needed since pan_active_viewport() / zoom_active_viewport() already write back to the entity.
✅ Step 3 — Paper-Space Coordinates → Pixel Conversion
pub fn viewport_screen_rect(&self, vp_handle: Handle, canvas_px: (f32, f32)) -> Option<iced::Rectangle>
Added to src/scene/mod.rs. Uses paper_limits() as the paper extent and scene.selection.borrow().vp_size as the live canvas size.
✅ Step 4 — paper_canvas_view Function
Added to src/app/view.rs. Builds a stack![] with:
shader(ViewportPane::paper_sheet(scene))— full-size paper background- One
shader(ViewportPane::paper(scene, handle))per viewport, positioned via Space offsets viewport_3dinview()is nowpaper_canvas_view(tab)whenis_paper
✅ Step 5 — Mouse Routing per Viewport
The full MSPACE routing chain is already wired and verified:
- Double-click inside a paper viewport →
Message::EnterViewport(handle)→scene.active_viewport = Some(handle)(update.rs:1704,2540) - Escape →
Message::ExitViewport→scene.active_viewport = None(update.rs:658) - Middle-drag in MSPACE →
pan_active_viewport()viaupdate.rs:1079–1080 - Scroll in MSPACE →
zoom_active_viewport()viaupdate.rs:1784–1792
camera_for_viewport() reads view_target / view_height fresh from the entity every frame, so the ViewportPane::Paper widget automatically reflects pan/zoom changes without any extra wiring.
✅ Step 6 — Per-Viewport Layer Freeze Verification
model_wires_for_viewport() (mod.rs:2208) uses the same handle-based filtering as the older entity_content_wires() (mod.rs:697):
- Reads
vp.frozen_layers: Vec<Handle>from the viewport entity - Looks up each entity's layer by name → gets its
Handle - Skips entities whose layer handle is in the frozen set
Both sets of handles come from the same CadDocument, so they match correctly. Global layer visibility (l.flags.off || l.flags.frozen) is also applied before the per-viewport check.
Implementation Order
- ✅
ViewportPane+ render helpers — model tab behavior unchanged - ✅ Per-viewport camera — derived from entity each frame
- ✅
viewport_screen_rect()— paper → pixel coordinate mapping - ✅
paper_canvas_view()— stack of PaperSheet + Paper widgets - ✅ Mouse routing verification for MSPACE in paper space
- ✅ Per-viewport layer freeze verification
Notes
CameraState(viewcube hover) is perViewportPaneinstance — Iced manages this automatically viashader::Program::State.- Paper-space entities (title blocks, frames) are rendered by
ViewportPane::PaperSheetusing the same paper-space camera as before. - The ViewCube is hidden in
ViewportPane::Papermode (only shown in Model and PaperSheet). selection_overlayand grip markers use paper-space screen coordinates — this is correct for PSPACE. For MSPACE, grips of model entities are projected through the paper camera, which may need adjustment in a future step.- Known limitation:
viewport_screen_rect()uses the last-rendered canvas size (scene.selection.borrow().vp_size), so viewport positions are correct from the second frame onward.