- Remove Scene: shader::Program<Msg> impl; all GPU rendering now goes
through ViewportPane which supports three modes:
Model — full model-space view (unchanged behaviour)
PaperSheet — paper-space entities only, using the paper camera
Paper — model content through a specific viewport's own camera
- Add Scene helper methods (render.rs):
build_primitive / build_paper_sheet_primitive / build_viewport_primitive
update_viewcube_state / viewcube_mouse_interaction
- Add Scene helpers (mod.rs):
paper_sheet_wires() — paper entities without viewport projection
camera_for_viewport() — derive Camera from Viewport entity data
model_wires_for_viewport() — model wires filtered by per-viewport frozen layers
viewport_screen_rect() — paper-space → pixel rect mapping
- Add paper_canvas_view() in view.rs:
Stack of PaperSheet + one Paper widget per Viewport entity,
positioned with Space offsets from viewport_screen_rect()
- Update VIEWPORT_WIDGET_PLAN.md: mark Steps 1–4 done, document remaining work
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.3 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 (TODO)
Currently the viewport_mouse mouse_area covers the full canvas for both model and paper space. MSPACE detection already works via Scene::active_viewport + viewport_at_paper_point(). What remains:
- Double-click on a paper viewport → enter MSPACE (set
active_viewport) - Escape → exit MSPACE (clear
active_viewport) - Pan/zoom in MSPACE → route to
pan_active_viewport()/zoom_active_viewport()
These are already partially wired — verify that MSPACE interactions reach the correct ViewportPane::Paper widget through the existing message handlers.
Step 6 — Per-Viewport Layer Freeze Verification (TODO)
build_viewport_primitive() calls model_wires_for_viewport() which filters by vp.frozen_layers. Verify that the frozen layer handles match the layer handles in the document (they should since both come from the same CadDocument).
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 - TODO Mouse routing verification for MSPACE in paper space
- TODO 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.