feat: replace paper-space shader with 2D Iced canvas widget
Paper space is now rendered as a 2D vector canvas (iced::widget::canvas) instead of a 3D shader widget. This allows direct interaction with paper-space entities (title blocks, viewport borders, annotations) without needing to enter MSPACE first. Changes: - src/scene/paper_canvas.rs: new PaperCanvas<'a> canvas::Program that renders paper wires (with linetype dashes), solid/gradient hatch fills, and wipeout backgrounds using the paper camera's orthographic transform. - Scene: three new public helpers (paper_canvas_wires, paper_canvas_hatches, paper_canvas_wipeouts) feed the canvas with the same data the old shader used, including inactive viewport projections and interim/preview wires. - view.rs: paper_canvas_view() now wraps PaperCanvas instead of shader(ViewportPane::paper_sheet(…)). The PaperViewportPane 3D shader overlay for the active MSPACE viewport is unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1c0520e4e4
commit
459b525ffe
3 changed files with 224 additions and 2 deletions
|
|
@ -4,9 +4,10 @@ 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::paper_canvas::PaperCanvas;
|
||||
use crate::scene::viewport_pane::{PaperViewportPane, ViewportPane};
|
||||
use crate::ui::overlay;
|
||||
use iced::widget::{button, column, container, mouse_area, row, shader, stack, text, Row, Space};
|
||||
use iced::widget::{button, canvas, column, container, mouse_area, row, shader, stack, text, Row, Space};
|
||||
use iced::window;
|
||||
use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Task, Theme};
|
||||
|
||||
|
|
@ -470,7 +471,10 @@ impl H7CAD {
|
|||
fn paper_canvas_view<'a>(tab: &'a super::document::DocumentTab) -> Element<'a, Message> {
|
||||
let scene = &tab.scene;
|
||||
|
||||
let paper_sheet = shader(ViewportPane::paper_sheet(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);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub mod hit_test;
|
|||
pub mod image_model;
|
||||
pub mod mesh_model;
|
||||
pub mod object;
|
||||
pub mod paper_canvas;
|
||||
pub mod pipeline;
|
||||
pub mod properties;
|
||||
mod render;
|
||||
|
|
@ -2214,6 +2215,30 @@ impl Scene {
|
|||
/// Paper-space entity wires only (title blocks, frames, borders).
|
||||
/// Does NOT include viewport content projection — that is handled by
|
||||
/// individual ViewportPane::Paper widgets layered on top.
|
||||
/// All wires needed to render the paper-space canvas (2D widget path).
|
||||
/// Includes paper entities, paper boundary, inactive viewport projections
|
||||
/// (excluding the active MSPACE viewport), plus interim/preview wires.
|
||||
pub fn paper_canvas_wires(&self) -> Vec<WireModel> {
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let mut wires = self.paper_sheet_wires();
|
||||
wires.extend(self.viewport_content_wires(layout_block, None, self.active_viewport));
|
||||
if let Some(iw) = &self.interim_wire {
|
||||
wires.push(iw.clone());
|
||||
}
|
||||
wires.extend(self.preview_wires.iter().cloned());
|
||||
wires
|
||||
}
|
||||
|
||||
/// Hatch fills for the paper-space canvas.
|
||||
pub fn paper_canvas_hatches(&self) -> Vec<HatchModel> {
|
||||
self.synced_hatch_models()
|
||||
}
|
||||
|
||||
/// Wipeout (opaque background fill) models for the paper-space canvas.
|
||||
pub fn paper_canvas_wipeouts(&self) -> Vec<HatchModel> {
|
||||
self.wipeout_models()
|
||||
}
|
||||
|
||||
pub(super) fn paper_sheet_wires(&self) -> Vec<WireModel> {
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let mut wires = self.wires_for_block(layout_block);
|
||||
|
|
|
|||
193
src/scene/paper_canvas.rs
Normal file
193
src/scene/paper_canvas.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
//! 2-D Iced canvas widget for the paper-space view.
|
||||
//!
|
||||
//! Replaces the 3-D shader widget used for the paper sheet so that paper-space
|
||||
//! entities (title blocks, annotations, viewport borders) can be interacted
|
||||
//! with directly — no need to enter MSPACE just to click on them.
|
||||
//!
|
||||
//! When MSPACE is active, a separate `PaperViewportPane` shader widget is
|
||||
//! stacked on top of this canvas for the active viewport's 3-D content
|
||||
//! (see `view.rs::paper_canvas_view()`).
|
||||
|
||||
use iced::widget::canvas;
|
||||
use iced::{mouse, Color, Point, Rectangle};
|
||||
|
||||
use super::hatch_model::{HatchModel, HatchPattern};
|
||||
use super::Scene;
|
||||
use crate::app::Message;
|
||||
|
||||
// ── PaperCanvas ───────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct PaperCanvas<'a> {
|
||||
pub scene: &'a Scene,
|
||||
}
|
||||
|
||||
impl<'a> PaperCanvas<'a> {
|
||||
pub fn new(scene: &'a Scene) -> Self {
|
||||
Self { scene }
|
||||
}
|
||||
}
|
||||
|
||||
// ── canvas::Program impl ──────────────────────────────────────────────────────
|
||||
|
||||
impl<'a> canvas::Program<Message> for PaperCanvas<'a> {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &iced::Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
// Update vp_size so viewport_screen_rect() knows the canvas dimensions
|
||||
// and can position the MSPACE shader overlay correctly.
|
||||
self.scene.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
|
||||
|
||||
let cam = self.scene.camera.borrow();
|
||||
let aspect = if bounds.height > 0.0 {
|
||||
bounds.width / bounds.height
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let half_h = cam.ortho_size();
|
||||
let half_w = half_h * aspect;
|
||||
let tx = cam.target.x;
|
||||
let ty = cam.target.y;
|
||||
drop(cam);
|
||||
|
||||
// Closure: paper-space world coords → canvas pixel coords.
|
||||
let to_px = move |wx: f32, wy: f32| Point {
|
||||
x: (wx - tx + half_w) / (2.0 * half_w) * bounds.width,
|
||||
y: (ty + half_h - wy) / (2.0 * half_h) * bounds.height,
|
||||
};
|
||||
|
||||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
||||
|
||||
// ── Background ────────────────────────────────────────────────────────
|
||||
let [r, g, b, a] = self.scene.paper_bg_color;
|
||||
frame.fill_rectangle(Point::ORIGIN, bounds.size(), Color { r, g, b, a });
|
||||
|
||||
// ── Wipeout fills (rendered before wires, cover background) ──────────
|
||||
for hatch in &self.scene.paper_canvas_wipeouts() {
|
||||
draw_hatch(&mut frame, hatch, &to_px);
|
||||
}
|
||||
|
||||
// ── Hatch fills ───────────────────────────────────────────────────────
|
||||
for hatch in &self.scene.paper_canvas_hatches() {
|
||||
draw_hatch(&mut frame, hatch, &to_px);
|
||||
}
|
||||
|
||||
// px-per-world-unit scale for linetype dash lengths.
|
||||
let world_to_px_scale = if half_w > 0.0 {
|
||||
bounds.width / (2.0 * half_w)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
// ── Wires (entity lines + inactive viewport projections) ──────────────
|
||||
for wire in &self.scene.paper_canvas_wires() {
|
||||
let [r, g, b, a] = wire.color;
|
||||
let color = Color { r, g, b, a };
|
||||
|
||||
let path = canvas::Path::new(|b| {
|
||||
let mut started = false;
|
||||
for &[wx, wy, _] in &wire.points {
|
||||
if wx.is_nan() || wy.is_nan() {
|
||||
started = false;
|
||||
continue;
|
||||
}
|
||||
let p = to_px(wx, wy);
|
||||
if started {
|
||||
b.line_to(p);
|
||||
} else {
|
||||
b.move_to(p);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Convert WireModel linetype pattern (world units) to pixel lengths.
|
||||
// Keep the Vec alive for the duration of frame.stroke().
|
||||
let dash_segments: Vec<f32> = if wire.pattern_length > 0.0 {
|
||||
wire.pattern
|
||||
.iter()
|
||||
.take_while(|&&v| v != 0.0)
|
||||
.map(|&v| v.abs() * world_to_px_scale)
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
frame.stroke(
|
||||
&path,
|
||||
canvas::Stroke {
|
||||
style: canvas::Style::Solid(color),
|
||||
width: wire.line_weight_px.max(1.0),
|
||||
line_cap: canvas::LineCap::Square,
|
||||
line_join: canvas::LineJoin::Miter,
|
||||
line_dash: canvas::LineDash {
|
||||
segments: &dash_segments,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Draw one HatchModel (solid, gradient, or pattern) onto `frame`.
|
||||
/// Patterns and gradients are approximated as solid fills in 2-D canvas mode.
|
||||
fn draw_hatch(
|
||||
frame: &mut canvas::Frame,
|
||||
hatch: &HatchModel,
|
||||
to_px: &impl Fn(f32, f32) -> Point,
|
||||
) {
|
||||
if hatch.boundary.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [r, g, b, a] = hatch.color;
|
||||
let color = Color { r, g, b, a };
|
||||
|
||||
let path = canvas::Path::new(|builder| {
|
||||
let first = to_px(hatch.boundary[0][0], hatch.boundary[0][1]);
|
||||
builder.move_to(first);
|
||||
for &[x, y] in &hatch.boundary[1..] {
|
||||
builder.line_to(to_px(x, y));
|
||||
}
|
||||
builder.close();
|
||||
});
|
||||
|
||||
match &hatch.pattern {
|
||||
HatchPattern::Solid => {
|
||||
frame.fill(&path, color);
|
||||
}
|
||||
HatchPattern::Pattern(_) => {
|
||||
// Pattern hatches: draw just the boundary outline for now.
|
||||
frame.stroke(
|
||||
&path,
|
||||
canvas::Stroke {
|
||||
style: canvas::Style::Solid(color),
|
||||
width: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
HatchPattern::Gradient { color2, .. } => {
|
||||
// Gradient: average the two colours as a solid fill.
|
||||
let avg = Color {
|
||||
r: (color.r + color2[0]) * 0.5,
|
||||
g: (color.g + color2[1]) * 0.5,
|
||||
b: (color.b + color2[2]) * 0.5,
|
||||
a: (color.a + color2[3]) * 0.5,
|
||||
};
|
||||
frame.fill(&path, avg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue