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>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-23 02:18:41 +03:00
1 parent de04b7e351
commit da9fafc0f8
5 files changed
+594 -81

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
use super::render::{CameraState, Primitive};
use super::Scene;
use acadrust::Handle;
use iced::widget::shader;
use iced::{mouse, Event, Rectangle};
// ── Mode ──────────────────────────────────────────────────────────────────
pub enum ViewportPaneMode {
/// Full model space — fills whatever bounds Iced assigns.
Model,
/// Paper-space entities only (title blocks, frames, borders) using the
/// paper-space camera. No viewport content projection.
PaperSheet,
/// Model-space content seen through a specific paper-space Viewport entity.
Paper { handle: Handle },
}
// ── Widget struct ─────────────────────────────────────────────────────────
pub struct ViewportPane<'a> {
pub scene: &'a Scene,
pub mode: ViewportPaneMode,
}
impl<'a> ViewportPane<'a> {
pub fn model(scene: &'a Scene) -> Self {
Self { scene, mode: ViewportPaneMode::Model }
}
/// Paper-sheet layer: paper-space entities rendered with the paper camera.
pub fn paper_sheet(scene: &'a Scene) -> Self {
Self { scene, mode: ViewportPaneMode::PaperSheet }
}
/// One paper-space viewport: model content rendered through its own camera.
pub fn paper(scene: &'a Scene, handle: Handle) -> Self {
Self { scene, mode: ViewportPaneMode::Paper { handle } }
}
}
// ── 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.mode {
ViewportPaneMode::Model => {
self.scene.build_primitive(state.hover_region, bounds)
}
ViewportPaneMode::PaperSheet => {
self.scene.build_paper_sheet_primitive(state.hover_region, bounds)
}
ViewportPaneMode::Paper { handle } => {
self.scene.build_viewport_primitive(*handle, state.hover_region, bounds)
}
}
}
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Option<iced::widget::Action<Msg>> {
// ViewCube hover only makes sense in the full model-space view.
if matches!(self.mode, ViewportPaneMode::Model | ViewportPaneMode::PaperSheet) {
self.scene.update_viewcube_state(state, bounds, cursor);
}
let _ = event;
None
}
fn mouse_interaction(
&self,
state: &Self::State,
_b: Rectangle,
_c: mouse::Cursor,
) -> mouse::Interaction {
if matches!(self.mode, ViewportPaneMode::Model | ViewportPaneMode::PaperSheet) {
self.scene.viewcube_mouse_interaction(state)
} else {
mouse::Interaction::default()
}
}
}