refactor(viewport): unify paper into one shader, transparent clear

Collapse the paper-space rendering path into the same shader widget the
model layout uses. The PaperCanvas widget draws the paper sheet, paper
entities and viewport borders underneath; the unified shader paints all
content viewports on top, scissored to each viewport's rect.

- Drop PaperViewportPane / PaperViewportPipeline newtype /
  PaperViewportPrimitive, ViewportPaneMode::Paper, ViewportPane::paper,
  build_primitive and build_viewport_primitive. Scene exposes only
  build_viewports.
- view.rs: the paper branch stacks PaperCanvas + the unified shader
  instead of the old paper_canvas_view function. The active paper
  viewport's blue border, render-mode picker and ViewCube hit-area are
  layered in the same viewport_stack as the model layout.
- Transparent shader clear (alpha=0) + alpha-blended blit pipeline so
  the underlying widget (container bg in model, PaperCanvas sheet in
  paper) shows through everywhere the shader has not painted geometry —
  including inside content viewport rectangles.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-28 10:03:01 +03:00
1 parent 417a708275
commit 8d64426087
5 files changed
+91 -455

No files matched your search

+50 -86
View File
@@ -5,7 +5,7 @@ use super::document::{DynComponent, DynFieldEntry};
use super::{Message, OpenCADStudio};
use crate::scene::grip::{grips_to_screen, grips_to_screen_paper};
use crate::scene::paper_canvas::PaperCanvas;
use crate::scene::viewport_pane::{PaperViewportPane, ViewportPane};
use crate::scene::viewport_pane::ViewportPane;
use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD};
use crate::ui::overlay;
use iced::widget::{
@@ -253,10 +253,13 @@ impl OpenCADStudio {
// Surrounding chrome (tab bar, status bar) stays; the welcome widget
// returned here also flags the rest of `view` to skip drawing-only
// overlays via `tab.is_start`.
// Unified GPU widget for both layouts. In a paper layout the
// PaperCanvas (2-D sheet + paper entities + viewport borders) is
// layered underneath this shader so the area outside floating
// viewports shows the sheet; the shader only blits inside each
// content viewport's scissor rect.
let viewport_3d: Element<'_, Message> = if tab.is_start {
start_page_view()
} else if is_paper {
paper_canvas_view(tab)
} else {
shader(ViewportPane::model(
&tab.scene,
@@ -457,6 +460,21 @@ impl OpenCADStudio {
.height(Fill)]
.width(Fill)
.height(Fill)
} else if is_paper {
// Paper layout: 2-D sheet + paper entities + viewport borders
// underneath, unified GPU shader on top (only paints inside
// each content viewport's scissor rect so the sheet shows
// through outside them).
let paper_sheet: Element<'_, Message> =
canvas(PaperCanvas::new(&tab.scene)).width(Fill).height(Fill).into();
stack![
paper_sheet,
container(viewport_3d).width(Fill).height(Fill),
selection_overlay,
viewport_mouse,
]
.width(Fill)
.height(Fill)
} else {
stack![
container(viewport_3d)
@@ -559,6 +577,35 @@ impl OpenCADStudio {
if let Some(rect) = active_vp_rect {
let x = rect.x.max(0.0);
let y = rect.y.max(0.0);
// Highlight the active viewport with a 2-px border so its
// boundary is always visible over the GPU shader.
const VP_BORDER: Color = Color {
r: 0.18,
g: 0.52,
b: 0.95,
a: 1.0,
};
let border_frame = container(
Space::new()
.width(iced::Length::Fixed(rect.width.max(1.0)))
.height(iced::Length::Fixed(rect.height.max(1.0))),
)
.style(move |_: &Theme| container::Style {
border: iced::Border {
color: VP_BORDER,
width: 2.0,
radius: 0.0.into(),
},
..Default::default()
});
let border_layer = column![
Space::new().height(iced::Length::Fixed(y)),
row![Space::new().width(iced::Length::Fixed(x)), border_frame,],
]
.width(Fill)
.height(Fill);
viewport_stack = viewport_stack.push(border_layer);
let vp_mode = tab
.scene
.active_viewport_render_mode()
@@ -932,89 +979,6 @@ impl OpenCADStudio {
}
}
// ── Paper canvas ──────────────────────────────────────────────────────────
//
// PSPACE: single full-canvas PaperSheet widget — renders paper entities plus
// model content of all viewports via CPU projection.
//
// MSPACE (active viewport): PaperSheet widget (excludes the active viewport
// from its CPU projection) + a PaperViewportPane widget overlaid at the
// active viewport's screen-space position. PaperViewportPane uses a
// distinct pipeline type (PaperViewportPipeline) so Iced's per-type storage
// keeps the two prepare() calls from overwriting each other.
fn paper_canvas_view<'a>(tab: &'a super::document::DocumentTab) -> Element<'a, Message> {
let scene = &tab.scene;
// 2-D canvas for the paper sheet — paper entities, viewport borders, and
// inactive viewport projections are rendered as vector paths. This lets
// users select/edit paper-space entities directly without entering MSPACE.
let paper_sheet = canvas(PaperCanvas::new(scene)).width(Fill).height(Fill);
if let Some(vp_handle) = scene.active_viewport {
let (canvas_w, canvas_h) = scene.selection.borrow().vp_size;
if let Some(rect) = scene.viewport_screen_rect(vp_handle, (canvas_w, canvas_h)) {
// Clamp to canvas bounds so Space widgets never get negative size.
let x = rect.x.max(0.0).min(canvas_w);
let y = rect.y.max(0.0).min(canvas_h);
let w = rect.width.clamp(1.0, canvas_w - x);
let h = rect.height.clamp(1.0, canvas_h - y);
let vp_widget = shader(PaperViewportPane::new(scene, vp_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);
// Blue border drawn on top of the 3-D overlay so the viewport
// boundary is always visible even when the shader fills the area.
const VP_BORDER: Color = Color {
r: 0.18,
g: 0.52,
b: 0.95,
a: 1.0,
};
let border_frame = container(
Space::new()
.width(iced::Length::Fixed(w))
.height(iced::Length::Fixed(h)),
)
.style(move |_: &Theme| container::Style {
border: iced::Border {
color: VP_BORDER,
width: 2.0,
radius: 0.0.into(),
},
..Default::default()
});
let border_layer = column![
Space::new().height(iced::Length::Fixed(y)),
row![Space::new().width(iced::Length::Fixed(x)), border_frame,],
]
.width(Fill)
.height(Fill);
// The render-mode picker and ViewCube hit area for the active
// viewport are layered in the main view stack (above the
// viewport mouse_area) so they're clickable — see
// `view()`. Here we only draw the sheet, the 3-D content, and
// the highlight border.
return stack![paper_sheet, positioned, border_layer]
.width(Fill)
.height(Fill)
.into();
}
}
paper_sheet.into()
}
// ── Document tab bar ───────────────────────────────────────────────────────
pub(super) fn doc_tab_bar<'a>(tabs: &'a [DocumentTab], active_tab: usize) -> Element<'a, Message> {