Files
OneCAD-Draft/VIEWPORT_WIDGET_PLAN.md
T
Hakan SevenandClaude Sonnet 4.6 da9fafc0f8 feat: introduce ViewportPane widget for unified model/paper rendering
- 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>
2026-04-23 02:18:41 +03:00

7.3 KiB
Raw Blame History

Unified ViewportPane Widget Plan

Current State (as of implementation start)

  • src/scene/render.rs: Scene implements shader::Program<Msg> → single full-screen GPU widget
  • src/app/view.rs:106: shader(&tab.scene).width(Fill).height(Fill) — same widget for both model and paper space, only render content differs
  • src/entities/viewport.rs: Viewport entity 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 (was draw())
  • build_paper_sheet_primitive() — paper entities only, no viewport projection
  • build_viewport_primitive(vp_handle) — model content through viewport camera
  • update_viewcube_state() / viewcube_mouse_interaction()

Scene helper methods added to mod.rs:

  • paper_sheet_wires() — paper-space entity wires without viewport content
  • camera_for_viewport(handle) — build Camera from Viewport entity data
  • model_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:

  1. shader(ViewportPane::paper_sheet(scene)) — full-size paper background
  2. One shader(ViewportPane::paper(scene, handle)) per viewport, positioned via Space offsets
  3. viewport_3d in view() is now paper_canvas_view(tab) when is_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

  1. ✅ ViewportPane + render helpers — model tab behavior unchanged
  2. ✅ Per-viewport camera — derived from entity each frame
  3. ✅ viewport_screen_rect() — paper → pixel coordinate mapping
  4. ✅ paper_canvas_view() — stack of PaperSheet + Paper widgets
  5. TODO Mouse routing verification for MSPACE in paper space
  6. TODO Per-viewport layer freeze verification

Notes

  • CameraState (viewcube hover) is per ViewportPane instance — Iced manages this automatically via shader::Program::State.
  • Paper-space entities (title blocks, frames) are rendered by ViewportPane::PaperSheet using the same paper-space camera as before.
  • The ViewCube is hidden in ViewportPane::Paper mode (only shown in Model and PaperSheet).
  • selection_overlay and 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.