forked from 1b-agent/opencadstudio-upstream
feat(render): phase 0 — render-mode pick_list + 3-flag pipeline plumbing
Replaces the binary Wireframe/Solid toggle with a 7-mode dropdown that maps onto acadrust's `ViewportRenderMode` enum: Wireframe 2D / Wireframe 3D / Hidden Line / Flat Shaded / Gouraud Shaded / Flat Shaded + Edges / Gouraud Shaded + Edges. Per-tab state lives on `DocumentTab::render_mode`; the old `SetWireframe` message forwards to `SetRenderMode` so the ribbon + WIREFRAME / SOLID command line keep working. `scene::render::render_mode_flags()` translates the mode into three pipeline-facing booleans (`face3d_fill`, `mesh_fill`, `show_3d_edges`); `mesh_fill` / `show_3d_edges` are stubs that the upcoming phases will consume. `Primitive` carries the derived `view_wireframe` plus the two new fields through to `prepare()`. `Pipeline::upload_face3d` takes a `wireframe_only` flag that drops 3DFACE quad fills (built from `key_vertices`) while keeping the `all_wires.fill_tris` channel — that channel carries text-LOD greek rectangles and PolyfaceMesh face triangles which must stay visible in wireframe mode. 3DFACE edges stay on regardless of mode. Solid3D mesh wireframe rendering, Hidden Line z-prepass, and Flat / Gouraud shader uniforms come in phases 1–4. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
5ea996d5fe
commit
2145ab8a66
8 files changed
+273
-90
No files matched your search
+3
-1
@@ -27,6 +27,7 @@ pub(super) struct DocumentTab {
|
||||
pub(super) selected_grips: Vec<GripDef>,
|
||||
pub(super) selected_handle: Option<Handle>,
|
||||
pub(super) wireframe: bool,
|
||||
pub(super) render_mode: acadrust::entities::ViewportRenderMode,
|
||||
pub(super) visual_style: String,
|
||||
pub(super) last_cursor_world: glam::Vec3,
|
||||
pub(super) last_cursor_screen: iced::Point,
|
||||
@@ -80,7 +81,8 @@ impl DocumentTab {
|
||||
selected_grips: vec![],
|
||||
selected_handle: None,
|
||||
wireframe: false,
|
||||
visual_style: "Shaded".into(),
|
||||
render_mode: acadrust::entities::ViewportRenderMode::Wireframe2D,
|
||||
visual_style: "Wireframe 2D".into(),
|
||||
last_cursor_world: glam::Vec3::ZERO,
|
||||
last_cursor_screen: iced::Point::ORIGIN,
|
||||
history: HistoryState::default(),
|
||||
|
||||
@@ -308,6 +308,10 @@ pub enum Message {
|
||||
SaveDialogCancel,
|
||||
ClearScene,
|
||||
SetWireframe(bool),
|
||||
/// Set the active tab's render mode (one of acadrust's seven visual
|
||||
/// styles). Replaces the binary `SetWireframe` over time; the older
|
||||
/// message stays for ribbon/CLI back-compat and forwards.
|
||||
SetRenderMode(acadrust::entities::ViewportRenderMode),
|
||||
/// Switch camera projection: true = Orthographic, false = Perspective.
|
||||
SetProjection(bool),
|
||||
/// Select a ribbon module tab by index.
|
||||
|
||||
+33
-11
@@ -654,19 +654,41 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::SetWireframe(w) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].wireframe = w;
|
||||
self.ribbon.set_wireframe(w);
|
||||
self.tabs[i].visual_style = if w {
|
||||
"Wireframe".into()
|
||||
// Back-compat shim: forward to the new render-mode path so
|
||||
// the ribbon button + WIREFRAME / SOLID command line still
|
||||
// work without duplicating the rendering plumbing.
|
||||
let mode = if w {
|
||||
acadrust::entities::ViewportRenderMode::Wireframe2D
|
||||
} else {
|
||||
"Shaded".into()
|
||||
acadrust::entities::ViewportRenderMode::FlatShaded
|
||||
};
|
||||
self.command_line.push_output(if w {
|
||||
"Visual style: Wireframe"
|
||||
} else {
|
||||
"Visual style: Shaded"
|
||||
});
|
||||
Task::done(Message::SetRenderMode(mode))
|
||||
}
|
||||
|
||||
Message::SetRenderMode(mode) => {
|
||||
use acadrust::entities::ViewportRenderMode as M;
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].render_mode = mode;
|
||||
// Keep the legacy `wireframe` bool synced — both wireframe
|
||||
// modes set it, everything else clears it.
|
||||
let wf = matches!(mode, M::Wireframe2D | M::Wireframe3D);
|
||||
self.tabs[i].wireframe = wf;
|
||||
self.ribbon.set_wireframe(wf);
|
||||
let label = match mode {
|
||||
M::Wireframe2D => "Wireframe 2D",
|
||||
M::Wireframe3D => "Wireframe 3D",
|
||||
M::HiddenLine => "Hidden Line",
|
||||
M::FlatShaded => "Flat Shaded",
|
||||
M::GouraudShaded => "Gouraud Shaded",
|
||||
M::FlatShadedWithEdges => "Flat Shaded + Edges",
|
||||
M::GouraudShadedWithEdges => "Gouraud Shaded + Edges",
|
||||
};
|
||||
self.tabs[i].visual_style = label.into();
|
||||
// Re-upload face3d fills on the next frame — the render
|
||||
// pipeline keys its upload cache off `geometry_epoch`.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.command_line
|
||||
.push_output(&format!("Visual style: {label}"));
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
||||
+91
-13
@@ -16,6 +16,27 @@ use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Tas
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX;
|
||||
|
||||
/// `pick_list` requires its items to implement `Display`; acadrust's
|
||||
/// `ViewportRenderMode` enum carries the raw DXF integers, not a label,
|
||||
/// so wrap it locally with a friendly name renderer.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(super) struct RenderModeChoice(pub acadrust::entities::ViewportRenderMode);
|
||||
|
||||
impl std::fmt::Display for RenderModeChoice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use acadrust::entities::ViewportRenderMode as M;
|
||||
f.write_str(match self.0 {
|
||||
M::Wireframe2D => "Wireframe 2D",
|
||||
M::Wireframe3D => "Wireframe 3D",
|
||||
M::HiddenLine => "Hidden Line",
|
||||
M::FlatShaded => "Flat Shaded",
|
||||
M::GouraudShaded => "Gouraud Shaded",
|
||||
M::FlatShadedWithEdges => "Flat Shaded + Edges",
|
||||
M::GouraudShadedWithEdges => "Gouraud Shaded + Edges",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub fn view(&self, window_id: window::Id) -> Element<'_, Message> {
|
||||
// ── Floating panel windows ─────────────────────────────────────────
|
||||
@@ -236,7 +257,11 @@ impl OpenCADStudio {
|
||||
} else if is_paper {
|
||||
paper_canvas_view(tab)
|
||||
} else {
|
||||
shader(ViewportPane::model(&tab.scene, self.show_viewcube))
|
||||
shader(ViewportPane::model(
|
||||
&tab.scene,
|
||||
self.show_viewcube,
|
||||
tab.render_mode,
|
||||
))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
@@ -357,15 +382,53 @@ impl OpenCADStudio {
|
||||
)
|
||||
};
|
||||
|
||||
let info = container(overlay::info_bar(
|
||||
if is_paper {
|
||||
&tab.scene.current_layout
|
||||
} else {
|
||||
"Custom View"
|
||||
// Render-mode picker, top-left of the viewport. Drives whether
|
||||
// 3D face / mesh fills and edges are rendered for the active
|
||||
// tab. Hatch fills are deliberately *not* gated by this — the
|
||||
// document's FILLMODE still owns 2D fill state.
|
||||
let render_modes: Vec<RenderModeChoice> = vec![
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::Wireframe2D),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::Wireframe3D),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::HiddenLine),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::FlatShaded),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::GouraudShaded),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::FlatShadedWithEdges),
|
||||
RenderModeChoice(acadrust::entities::ViewportRenderMode::GouraudShadedWithEdges),
|
||||
];
|
||||
let info = container(
|
||||
iced::widget::pick_list(
|
||||
render_modes,
|
||||
Some(RenderModeChoice(tab.render_mode)),
|
||||
|c| Message::SetRenderMode(c.0),
|
||||
)
|
||||
.text_size(11),
|
||||
)
|
||||
.style(|_: &Theme| iced::widget::container::Style {
|
||||
background: Some(iced::Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 0.75,
|
||||
})),
|
||||
border: iced::Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
&tab.visual_style,
|
||||
))
|
||||
.padding([4, 6]);
|
||||
text_color: Some(Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 8]);
|
||||
|
||||
let viewport_mouse = mouse_area(container(
|
||||
iced::widget::Space::new().width(Fill).height(Fill),
|
||||
@@ -443,20 +506,35 @@ impl OpenCADStudio {
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
container(info).width(Fill).height(Fill),
|
||||
selection_overlay,
|
||||
viewport_mouse,
|
||||
// Toggle sits ABOVE the viewport mouse_area so clicks
|
||||
// inside the toggle's bounds reach it instead of the
|
||||
// shader behind it; `opaque` blocks the same clicks from
|
||||
// bubbling further down. Outside the toggle's footprint
|
||||
// the outer Fill container is transparent — viewport
|
||||
// drawing / selection stays unaffected.
|
||||
container(iced::widget::opaque(info))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::alignment::Horizontal::Left)
|
||||
.align_y(iced::alignment::Vertical::Top),
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
};
|
||||
|
||||
if self.show_navbar && !tab.is_start {
|
||||
// Anchor the nav toolbar to the vertical centre of the
|
||||
// viewport area, flush against the right edge with a small
|
||||
// gutter, so it stays aligned no matter how tall the
|
||||
// window is.
|
||||
let nav = container(overlay::nav_toolbar())
|
||||
.align_right(Fill)
|
||||
.align_top(Fill)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
.height(Fill)
|
||||
.padding(iced::Padding {
|
||||
top: 148.0,
|
||||
top: 0.0,
|
||||
right: 8.0,
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
@@ -805,7 +883,7 @@ fn paper_canvas_view<'a>(tab: &'a super::document::DocumentTab) -> Element<'a, M
|
||||
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))
|
||||
let vp_widget = shader(PaperViewportPane::new(scene, vp_handle, tab.render_mode))
|
||||
.width(iced::Length::Fixed(w))
|
||||
.height(iced::Length::Fixed(h));
|
||||
|
||||
|
||||
@@ -784,16 +784,25 @@ impl Pipeline {
|
||||
device: &wgpu::Device,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
wireframe_only: bool,
|
||||
) {
|
||||
let has_fills =
|
||||
!face3d_wires.is_empty() || all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
||||
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
||||
// outlines stay on the screen regardless of mode.
|
||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
||||
// Fill buffer: in wireframe mode we drop the 3DFACE quads
|
||||
// (those come from `face3d_wires.key_vertices`) but keep
|
||||
// `all_wires.fill_tris` — that channel carries 2D content like
|
||||
// text-LOD greek rectangles and PolyfaceMesh face triangles
|
||||
// which shouldn't disappear with the toggle.
|
||||
let face3d_for_fill: &[WireModel] = if wireframe_only { &[] } else { face3d_wires };
|
||||
let has_fills = !face3d_for_fill.is_empty()
|
||||
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
||||
if !has_fills {
|
||||
self.gpu_face3d_fill = None;
|
||||
self.gpu_face3d_edges = vec![];
|
||||
return;
|
||||
} else {
|
||||
self.gpu_face3d_fill =
|
||||
Some(Face3DGpu::from_wires(device, face3d_for_fill, all_wires));
|
||||
}
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires, all_wires));
|
||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
||||
}
|
||||
|
||||
pub fn upload_meshes(&mut self, device: &wgpu::Device, meshes: &[MeshLodSet]) {
|
||||
|
||||
+89
-8
@@ -98,6 +98,22 @@ pub struct Primitive {
|
||||
/// Header.fill_mode (FILLMODE): when false, hatch / wipeout / face3d-fill
|
||||
/// uploads short-circuit so the renderer draws only wireframe.
|
||||
pub(super) fill_mode: bool,
|
||||
/// Per-view "Wireframe vs Solid" toggle. When `true`, 3D face fills
|
||||
/// are dropped on the upload path so 3D faces draw as edges only.
|
||||
/// Hatch / wipeout uploads are deliberately *not* gated by this flag —
|
||||
/// the user toggle should only affect 3D solids, not 2D fills.
|
||||
pub(super) view_wireframe: bool,
|
||||
/// Whether the active render mode wants 3D mesh fills uploaded. Off
|
||||
/// in `Wireframe2D` / `Wireframe3D`; on for every shaded variant. Set
|
||||
/// at the same point `view_wireframe` is computed so the two stay in
|
||||
/// lock-step for the gating logic in `prepare()`.
|
||||
#[allow(dead_code)]
|
||||
pub(super) mesh_fill: bool,
|
||||
/// Whether the active render mode wants 3D mesh / face edges
|
||||
/// rendered on top of fills. Most shaded modes turn this off; the
|
||||
/// `*WithEdges` variants and the pure wireframes leave it on.
|
||||
#[allow(dead_code)]
|
||||
pub(super) show_3d_edges: bool,
|
||||
pub(super) geometry_epoch: u64,
|
||||
/// Camera generation captured when this Primitive was assembled. Paired
|
||||
/// with `geometry_epoch` so the wire buffers re-upload when the view
|
||||
@@ -105,6 +121,48 @@ pub struct Primitive {
|
||||
pub(super) camera_generation: u64,
|
||||
}
|
||||
|
||||
/// Flags the render pipeline consumes, derived from
|
||||
/// [`acadrust::entities::ViewportRenderMode`]. Each shaded variant fills
|
||||
/// 3D faces and meshes; the pure wireframes drop the fill and keep only
|
||||
/// edges. `*WithEdges` variants render both. `HiddenLine` is currently
|
||||
/// rendered as plain solid (until Phase 2 wires up the depth-only
|
||||
/// prepass); `FlatShaded` vs `GouraudShaded` differ in shader uniform
|
||||
/// only and produce identical fill flags here.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RenderModeFlags {
|
||||
pub face3d_fill: bool,
|
||||
pub mesh_fill: bool,
|
||||
pub show_3d_edges: bool,
|
||||
}
|
||||
|
||||
pub fn render_mode_flags(
|
||||
mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> RenderModeFlags {
|
||||
use acadrust::entities::ViewportRenderMode as M;
|
||||
match mode {
|
||||
M::Wireframe2D | M::Wireframe3D => RenderModeFlags {
|
||||
face3d_fill: false,
|
||||
mesh_fill: false,
|
||||
show_3d_edges: true,
|
||||
},
|
||||
M::HiddenLine => RenderModeFlags {
|
||||
face3d_fill: true,
|
||||
mesh_fill: true,
|
||||
show_3d_edges: true,
|
||||
},
|
||||
M::FlatShaded | M::GouraudShaded => RenderModeFlags {
|
||||
face3d_fill: true,
|
||||
mesh_fill: true,
|
||||
show_3d_edges: false,
|
||||
},
|
||||
M::FlatShadedWithEdges | M::GouraudShadedWithEdges => RenderModeFlags {
|
||||
face3d_fill: true,
|
||||
mesh_fill: true,
|
||||
show_3d_edges: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ── shader::Primitive impl ────────────────────────────────────────────────
|
||||
|
||||
impl shader::Primitive for Primitive {
|
||||
@@ -132,6 +190,11 @@ impl shader::Primitive for Primitive {
|
||||
pipeline.upload_uniforms(queue, &self.uniforms);
|
||||
let cur_key = (self.geometry_epoch, self.camera_generation);
|
||||
let fill_mode = self.fill_mode;
|
||||
// 3D face fill requires *both* the doc-level FILLMODE *and* the
|
||||
// per-view Solid toggle. Hatches / wipeouts deliberately ignore
|
||||
// the view toggle so 2D fills stay on even when the user picks
|
||||
// the Wireframe overlay style.
|
||||
let face3d_fill_active = fill_mode && !self.view_wireframe;
|
||||
if cur_key != pipeline.cached_epoch {
|
||||
// Static buffers (hatches/images/meshes) only need refresh on a
|
||||
// real geometry change, not on every camera tick.
|
||||
@@ -149,12 +212,15 @@ impl shader::Primitive for Primitive {
|
||||
// Wires re-upload on every camera change because the visible
|
||||
// subset shifts under frustum culling.
|
||||
pipeline.upload_wires(device, &self.wires[..]);
|
||||
if fill_mode {
|
||||
pipeline.upload_face3d(device, &self.face3d_wires[..], &self.wires[..]);
|
||||
} else {
|
||||
// Edges still need to draw, but no fill_tris are forwarded.
|
||||
pipeline.upload_face3d(device, &self.face3d_wires[..], &[]);
|
||||
}
|
||||
// `wireframe_only=true` keeps the face3d edge buffer but
|
||||
// drops the fill — that's the on-screen result of toggling
|
||||
// the render mode to Wireframe2D / Wireframe3D.
|
||||
pipeline.upload_face3d(
|
||||
device,
|
||||
&self.face3d_wires[..],
|
||||
&self.wires[..],
|
||||
!face3d_fill_active,
|
||||
);
|
||||
pipeline.cached_epoch = cur_key;
|
||||
}
|
||||
pipeline.compute_wire_scissors(self.uniforms.view_proj, clip_size.width, clip_size.height);
|
||||
@@ -337,7 +403,10 @@ impl Scene {
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
show_viewcube: bool,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> Primitive {
|
||||
let flags = render_mode_flags(render_mode);
|
||||
let view_wireframe = !flags.face3d_fill;
|
||||
let cam = self.camera.borrow();
|
||||
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
|
||||
// Record the active widget's aspect so view_world_aabb() can compute
|
||||
@@ -379,6 +448,9 @@ impl Scene {
|
||||
bg_color,
|
||||
show_viewcube,
|
||||
fill_mode: self.document.header.fill_mode,
|
||||
view_wireframe,
|
||||
mesh_fill: flags.mesh_fill,
|
||||
show_3d_edges: flags.show_3d_edges,
|
||||
geometry_epoch: self.geometry_epoch,
|
||||
camera_generation: self.camera_generation,
|
||||
}
|
||||
@@ -391,10 +463,13 @@ impl Scene {
|
||||
vp_handle: Handle,
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> Primitive {
|
||||
let flags = render_mode_flags(render_mode);
|
||||
let view_wireframe = !flags.face3d_fill;
|
||||
let cam = match self.camera_for_viewport(vp_handle) {
|
||||
Some(c) => c,
|
||||
None => return self.build_primitive(hover_region, bounds, false),
|
||||
None => return self.build_primitive(hover_region, bounds, false, render_mode),
|
||||
};
|
||||
|
||||
let base_arc = self.model_wires_for_viewport_arc(vp_handle);
|
||||
@@ -423,6 +498,9 @@ impl Scene {
|
||||
bg_color: self.bg_color,
|
||||
show_viewcube: false,
|
||||
fill_mode: self.document.header.fill_mode,
|
||||
view_wireframe,
|
||||
mesh_fill: flags.mesh_fill,
|
||||
show_3d_edges: flags.show_3d_edges,
|
||||
geometry_epoch: self.geometry_epoch,
|
||||
camera_generation: self.camera_generation,
|
||||
}
|
||||
@@ -435,8 +513,11 @@ impl Scene {
|
||||
vp_handle: Handle,
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> PaperViewportPrimitive {
|
||||
PaperViewportPrimitive(self.build_viewport_primitive(vp_handle, hover_region, bounds))
|
||||
PaperViewportPrimitive(
|
||||
self.build_viewport_primitive(vp_handle, hover_region, bounds, render_mode),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update viewcube hover state from cursor position within `bounds`.
|
||||
|
||||
+37
-12
@@ -20,14 +20,20 @@ pub struct ViewportPane<'a> {
|
||||
pub scene: &'a Scene,
|
||||
pub mode: ViewportPaneMode,
|
||||
pub show_viewcube: bool,
|
||||
pub render_mode: acadrust::entities::ViewportRenderMode,
|
||||
}
|
||||
|
||||
impl<'a> ViewportPane<'a> {
|
||||
pub fn model(scene: &'a Scene, show_viewcube: bool) -> Self {
|
||||
pub fn model(
|
||||
scene: &'a Scene,
|
||||
show_viewcube: bool,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> Self {
|
||||
Self {
|
||||
scene,
|
||||
mode: ViewportPaneMode::Model,
|
||||
show_viewcube,
|
||||
render_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +45,7 @@ impl<'a> ViewportPane<'a> {
|
||||
scene,
|
||||
mode: ViewportPaneMode::Paper { handle },
|
||||
show_viewcube: false,
|
||||
render_mode: acadrust::entities::ViewportRenderMode::Wireframe2D,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,11 +59,20 @@ impl<'a> ViewportPane<'a> {
|
||||
pub struct PaperViewportPane<'a> {
|
||||
pub scene: &'a Scene,
|
||||
pub handle: Handle,
|
||||
pub render_mode: acadrust::entities::ViewportRenderMode,
|
||||
}
|
||||
|
||||
impl<'a> PaperViewportPane<'a> {
|
||||
pub fn new(scene: &'a Scene, handle: Handle) -> Self {
|
||||
Self { scene, handle }
|
||||
pub fn new(
|
||||
scene: &'a Scene,
|
||||
handle: Handle,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> Self {
|
||||
Self {
|
||||
scene,
|
||||
handle,
|
||||
render_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +87,12 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPan
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
self.scene
|
||||
.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
|
||||
.build_active_viewport_primitive(
|
||||
self.handle,
|
||||
state.hover_region,
|
||||
bounds,
|
||||
self.render_mode,
|
||||
)
|
||||
}
|
||||
|
||||
fn update(
|
||||
@@ -109,14 +130,18 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
match &self.mode {
|
||||
ViewportPaneMode::Model => {
|
||||
self.scene
|
||||
.build_primitive(state.hover_region, bounds, self.show_viewcube)
|
||||
}
|
||||
ViewportPaneMode::Paper { handle } => {
|
||||
self.scene
|
||||
.build_viewport_primitive(*handle, state.hover_region, bounds)
|
||||
}
|
||||
ViewportPaneMode::Model => self.scene.build_primitive(
|
||||
state.hover_region,
|
||||
bounds,
|
||||
self.show_viewcube,
|
||||
self.render_mode,
|
||||
),
|
||||
ViewportPaneMode::Paper { handle } => self.scene.build_viewport_primitive(
|
||||
*handle,
|
||||
state.hover_region,
|
||||
bounds,
|
||||
self.render_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-39
@@ -1,10 +1,9 @@
|
||||
//! Viewport overlay widgets:
|
||||
//! • info_bar() — top-left bracket buttons showing view name / style
|
||||
//! • nav_toolbar() — vertical orbit/pan/zoom buttons on the right
|
||||
|
||||
use glam::{Mat4, Vec3};
|
||||
use iced::mouse;
|
||||
use iced::widget::{button, canvas, column, container, row, text};
|
||||
use iced::widget::{button, canvas, column, container, text};
|
||||
use iced::{Background, Border, Color, Element, Length, Point, Size, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
@@ -30,43 +29,6 @@ pub struct GripMarker {
|
||||
pub is_hot: bool,
|
||||
}
|
||||
|
||||
// ── Info bar ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn info_bar<'a>(view_name: &'a str, visual_style: &'a str) -> Element<'a, Message> {
|
||||
let bracket = |s: &'a str| -> Element<'a, Message> {
|
||||
container(text(s).size(10).color(Color {
|
||||
r: 0.78,
|
||||
g: 0.78,
|
||||
b: 0.78,
|
||||
a: 1.0,
|
||||
}))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 0.75,
|
||||
})),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 6])
|
||||
.into()
|
||||
};
|
||||
row![bracket("[-]"), bracket(view_name), bracket(visual_style)]
|
||||
.spacing(0)
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Nav toolbar ───────────────────────────────────────────────────────────
|
||||
|
||||
pub fn nav_toolbar<'a>() -> Element<'a, Message> {
|
||||
|
||||
Reference in new issue
Block a user