fix: restore paper-space viewport rendering as single full-canvas widget

Iced 0.14 batches all shader prepare() calls before any render() calls.
Multiple ViewportPane widgets sharing the same Pipeline type overwrite
each other's GPU buffers, causing the PaperSheet layer (full-canvas) to
render the last viewport's model content — appearing full-screen.

Fix: paper_canvas_view() now renders a single full-canvas PaperSheet
widget. build_paper_sheet_primitive() is updated to include model-space
content projected through each viewport's view matrix (via
viewport_content_wires()), restoring the original working behaviour.

ViewportPane::Paper mode and viewport_screen_rect() are kept with
#[allow(dead_code)] — a per-viewport wgpu sub-renderer that accumulates
data across frames could revive them in the future.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-23 03:02:01 +03:00
1 parent 592e3da6ac
commit d1d4bb3d02
4 files changed
+33 -62

No files matched your search

+13 -52
View File
@@ -457,61 +457,22 @@ 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).
// Renders the full paper canvas as a single full-size shader widget using the
// PaperSheet mode, which includes both paper-space entities (title blocks,
// frames, borders) and model-space content projected through each viewport's
// view matrix into paper-space coordinates.
//
// Note: A per-viewport widget approach (ViewportPane::Paper) was attempted but
// does not work correctly because Iced 0.14 batches all shader prepare() calls
// before any render() calls, causing widgets that share the same Pipeline type
// to overwrite each other's GPU buffers. The CPU-projection approach used here
// is the correct solution within Iced's shader framework.
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))
shader(ViewportPane::paper_sheet(&tab.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()
.height(Fill)
.into()
}
// ── Document tab bar ───────────────────────────────────────────────────────
+3 -3
View File
@@ -2103,9 +2103,9 @@ impl Scene {
/// `Rectangle` relative to the top-left of the paper canvas.
///
/// `canvas_px` — the pixel dimensions of the area that shows the paper.
/// Paper limits from the current layout define the paper-space extent;
/// the viewport is mapped linearly into that area (Y-axis flipped).
/// Returns `None` if the viewport handle is invalid or paper limits are missing.
/// Map a paper-space viewport into screen-pixel coordinates.
/// Needed when per-viewport shader widgets are revived (see `ViewportPaneMode::Paper`).
#[allow(dead_code)]
pub fn viewport_screen_rect(
&self,
vp_handle: Handle,
+5 -4
View File
@@ -196,10 +196,9 @@ impl Scene {
}
}
/// Build a Primitive for the paper-sheet background: paper-space entities
/// (title blocks, frames, borders) using the paper-space camera.
/// Viewport content is NOT included — it is rendered by separate
/// `ViewportPane::Paper` widgets stacked on top.
/// Build a Primitive for the full paper canvas: paper-space entities
/// (title blocks, frames, borders) plus model-space content projected
/// through each viewport's view matrix into paper-space coordinates.
pub(super) fn build_paper_sheet_primitive(
&self,
hover_region: Option<usize>,
@@ -208,7 +207,9 @@ impl Scene {
let cam = self.camera.borrow();
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
let layout_block = self.current_layout_block_handle();
let mut wires = self.paper_sheet_wires();
wires.extend(self.viewport_content_wires(layout_block, None));
if let Some(iw) = &self.interim_wire {
wires.push(iw.clone());
}
+12 -3
View File
@@ -9,10 +9,17 @@ use iced::{mouse, Event, Rectangle};
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.
/// Paper-space entities plus model content projected through viewports.
/// Used for the full paper canvas (single widget, no per-viewport widgets).
PaperSheet,
/// Model-space content seen through a specific paper-space Viewport entity.
/// Model-space content rendered through a specific viewport's 3-D camera.
///
/// NOTE: Currently unused because Iced 0.14 batches all shader `prepare()`
/// calls before any `render()` calls. Multiple widgets sharing the same
/// `Pipeline` type overwrite each other's GPU buffers. A per-viewport
/// wgpu sub-renderer that accumulates data across frames would be needed to
/// revive this path.
#[allow(dead_code)]
Paper { handle: Handle },
}
@@ -34,6 +41,8 @@ impl<'a> ViewportPane<'a> {
}
/// One paper-space viewport: model content rendered through its own camera.
/// See [`ViewportPaneMode::Paper`] for why this is currently unused.
#[allow(dead_code)]
pub fn paper(scene: &'a Scene, handle: Handle) -> Self {
Self { scene, mode: ViewportPaneMode::Paper { handle } }
}