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 <[email protected]>
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

+68 -1
View File
@@ -4,6 +4,7 @@ use super::history::history_dropdown_labels;
use super::helpers::grid_plane_from_camera;
use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD};
use crate::scene::grip::grips_to_screen;
use crate::scene::viewport_pane::ViewportPane;
use crate::ui::overlay;
use iced::widget::{button, column, container, mouse_area, row, shader, stack, text, Row, Space};
use iced::window;
@@ -103,7 +104,14 @@ impl H7CAD {
let i = self.active_tab;
let tab = &self.tabs[i];
let is_paper = tab.scene.current_layout != "Model";
let viewport_3d = shader(&tab.scene).width(Fill).height(Fill);
let viewport_3d: Element<'_, Message> = if is_paper {
paper_canvas_view(tab)
} else {
shader(ViewportPane::model(&tab.scene))
.width(Fill)
.height(Fill)
.into()
};
let selection_overlay = {
let sel = tab.scene.selection.borrow().clone();
@@ -447,6 +455,65 @@ impl H7CAD {
}
}
// ── Paper canvas ──────────────────────────────────────────────────────────
//
// In paper space, the main canvas is composed of:
// 1. A full-size PaperSheet layer — renders paper-space entities (title blocks,
// frames, borders) with the paper-space camera.
// 2. One ViewportPane::Paper widget per Viewport entity, positioned at its
// paper-space coordinates and sized to its paper-space dimensions.
// Each renders model-space content through its own viewport camera.
// 3. The existing selection_overlay / viewport_mouse / nav stack on top
// (unchanged — mouse event routing stays the same).
fn paper_canvas_view<'a>(tab: &'a super::document::DocumentTab) -> Element<'a, Message> {
let scene = &tab.scene;
// Layer 1: paper-space entities (title blocks, frames, paper boundary).
let paper_sheet = shader(ViewportPane::paper_sheet(scene))
.width(Fill)
.height(Fill);
// Use the last-rendered canvas size to compute pixel positions.
// On the very first frame this is (0,0); correct positions appear from
// the second frame onward (imperceptible in practice).
let (canvas_w, canvas_h) = scene.selection.borrow().vp_size;
let vp_list = scene.viewport_list();
let mut layers = stack![paper_sheet];
for (handle, _, _) in &vp_list {
let Some(rect) = scene.viewport_screen_rect(*handle, (canvas_w, canvas_h)) else {
continue;
};
// Clamp to avoid zero-size or negative widgets.
let w = rect.width.max(1.0);
let h = rect.height.max(1.0);
let x = rect.x.max(0.0);
let y = rect.y.max(0.0);
// Layer 2+: one shader widget per viewport, positioned with Space offsets.
let vp_widget = shader(ViewportPane::paper(scene, *handle))
.width(iced::Length::Fixed(w))
.height(iced::Length::Fixed(h));
let positioned = column![
Space::new().height(iced::Length::Fixed(y)),
row![
Space::new().width(iced::Length::Fixed(x)),
vp_widget,
],
]
.width(Fill)
.height(Fill);
layers = layers.push(positioned);
}
layers.width(Fill).height(Fill).into()
}
// ── Document tab bar ───────────────────────────────────────────────────────
pub(super) fn doc_tab_bar<'a>(tabs: &'a [DocumentTab], active_tab: usize) -> Element<'a, Message> {