forked from 1b-agent/opencadstudio-upstream
feat(paper): render-mode picker + ViewCube on active viewport
Paper-space'de bir viewport'a çift tıklanıp aktif hale gelince, model space'deki gibi sol üst köşede render-mode dropdown'ı ve sağ üst köşede ViewCube gösteriliyor. - render_mode_picker helper'a çıkarıldı; model space ve aktif paper viewport ortak kullanıyor (kod tekrarı yok) - SetRenderMode artık viewport-aware: aktif viewport varsa o viewport entity'sinin render_mode'unu, yoksa model-layout tab stilini günceller (Scene::active_viewport_render_mode getter/setter) - build_viewport_primitive'e show_viewcube parametresi; aktif viewport primitive'i ViewCube gizmo'sunu çiziyor - PaperViewportPane shader::Program artık Message'a özelleşti ve sol tık ViewCube hit-test'ini bounds-relative yapıp ViewCubeSnap üretiyor Bilinen sorun: picker/viewcube doğru konumda görünüyor ancak tıklamalar üstteki viewport_mouse mouse_area katmanına gidiyor; katman sırası bir sonraki adımda düzeltilecek. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
fda0f2dbb9
commit
a41a05baa1
5 files changed
+149
-57
No files matched your search
+15
-6
@@ -669,12 +669,6 @@ impl OpenCADStudio {
|
||||
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",
|
||||
@@ -684,6 +678,21 @@ impl OpenCADStudio {
|
||||
M::FlatShadedWithEdges => "Flat Shaded + Edges",
|
||||
M::GouraudShadedWithEdges => "Gouraud Shaded + Edges",
|
||||
};
|
||||
// In a paper layout with an active (double-clicked)
|
||||
// viewport, the picker drives that viewport entity's own
|
||||
// render mode; the model-layout tab style is untouched.
|
||||
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.command_line
|
||||
.push_output(&format!("Viewport visual style: {label}"));
|
||||
return Task::none();
|
||||
}
|
||||
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);
|
||||
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`.
|
||||
|
||||
+72
-44
@@ -387,49 +387,7 @@ impl OpenCADStudio {
|
||||
// 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(),
|
||||
},
|
||||
text_color: Some(Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 8]);
|
||||
let info = render_mode_picker(tab.render_mode);
|
||||
|
||||
let viewport_mouse = mouse_area(container(
|
||||
iced::widget::Space::new().width(Fill).height(Fill),
|
||||
@@ -931,7 +889,23 @@ fn paper_canvas_view<'a>(tab: &'a super::document::DocumentTab) -> Element<'a, M
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
return stack![paper_sheet, positioned, border_layer]
|
||||
// Render-mode picker in the active viewport's top-left corner,
|
||||
// mirroring the model-space overlay. Reads / writes the
|
||||
// viewport entity's own render mode via SetRenderMode.
|
||||
let vp_mode = scene
|
||||
.active_viewport_render_mode()
|
||||
.unwrap_or(acadrust::entities::ViewportRenderMode::Wireframe2D);
|
||||
let picker_layer = column![
|
||||
Space::new().height(iced::Length::Fixed(y + 4.0)),
|
||||
row![
|
||||
Space::new().width(iced::Length::Fixed(x + 4.0)),
|
||||
iced::widget::opaque(render_mode_picker(vp_mode)),
|
||||
],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
return stack![paper_sheet, positioned, border_layer, picker_layer]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into();
|
||||
@@ -2017,6 +1991,60 @@ pub(super) fn recent_files_panel<'a>(recents: &'a [std::path::PathBuf]) -> Eleme
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Render-mode picker ──────────────────────────────────────────────────────
|
||||
|
||||
/// The 7-way visual-style `pick_list`, styled as a dark overlay chip.
|
||||
/// Shared by the model-space viewport (top-left) and each active
|
||||
/// paper-space viewport. Emits `SetRenderMode`, which the update loop
|
||||
/// routes to the active viewport entity or the model-layout tab.
|
||||
fn render_mode_picker<'a>(
|
||||
current: acadrust::entities::ViewportRenderMode,
|
||||
) -> Element<'a, Message> {
|
||||
use acadrust::entities::ViewportRenderMode as M;
|
||||
let render_modes: Vec<RenderModeChoice> = vec![
|
||||
RenderModeChoice(M::Wireframe2D),
|
||||
RenderModeChoice(M::Wireframe3D),
|
||||
RenderModeChoice(M::HiddenLine),
|
||||
RenderModeChoice(M::FlatShaded),
|
||||
RenderModeChoice(M::GouraudShaded),
|
||||
RenderModeChoice(M::FlatShadedWithEdges),
|
||||
RenderModeChoice(M::GouraudShadedWithEdges),
|
||||
];
|
||||
container(
|
||||
iced::widget::pick_list(render_modes, Some(RenderModeChoice(current)), |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(),
|
||||
},
|
||||
text_color: Some(Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Dynamic-input field formatting ─────────────────────────────────────────
|
||||
|
||||
/// Short prefix shown before a dynamic-input box's value.
|
||||
|
||||
@@ -2199,6 +2199,36 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render mode of the active paper-space viewport, or `None` when no
|
||||
/// viewport is active (PSPACE / model layout).
|
||||
pub fn active_viewport_render_mode(
|
||||
&self,
|
||||
) -> Option<acadrust::entities::ViewportRenderMode> {
|
||||
let h = self.active_viewport?;
|
||||
match self.document.get_entity(h) {
|
||||
Some(acadrust::EntityType::Viewport(vp)) => Some(vp.render_mode),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the active paper-space viewport's render mode. Returns `true`
|
||||
/// when a viewport was active and updated; `false` (no-op) otherwise,
|
||||
/// so the caller can fall back to the model-layout render mode.
|
||||
pub fn set_active_viewport_render_mode(
|
||||
&mut self,
|
||||
mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> bool {
|
||||
let Some(h) = self.active_viewport else {
|
||||
return false;
|
||||
};
|
||||
if let Some(acadrust::EntityType::Viewport(vp)) = self.document.get_entity_mut(h) {
|
||||
vp.render_mode = mode;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// View-rotation matrix for the active viewport (MSPACE), or the
|
||||
/// paper-space camera's matrix when not in MSPACE.
|
||||
/// Used by ViewCube hit-testing so clicks map to the correct camera.
|
||||
|
||||
+5
-2
@@ -512,6 +512,7 @@ impl Scene {
|
||||
vp_handle: Handle,
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
show_viewcube: bool,
|
||||
) -> Primitive {
|
||||
let render_mode = match self.document.get_entity(vp_handle) {
|
||||
Some(EntityType::Viewport(vp)) => vp.render_mode,
|
||||
@@ -551,7 +552,7 @@ impl Scene {
|
||||
cam_rotation: cam.view_rotation_mat(),
|
||||
hover_region,
|
||||
bg_color: self.bg_color,
|
||||
show_viewcube: false,
|
||||
show_viewcube,
|
||||
fill_mode: self.document.header.fill_mode,
|
||||
view_wireframe,
|
||||
mesh_fill: flags.mesh_fill,
|
||||
@@ -570,8 +571,10 @@ impl Scene {
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
) -> PaperViewportPrimitive {
|
||||
// The active (double-clicked) viewport shows the ViewCube gizmo,
|
||||
// mirroring the model-space view.
|
||||
PaperViewportPrimitive(
|
||||
self.build_viewport_primitive(vp_handle, hover_region, bounds),
|
||||
self.build_viewport_primitive(vp_handle, hover_region, bounds, true),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::render::{CameraState, PaperViewportPrimitive, Primitive};
|
||||
use super::Scene;
|
||||
use super::{Scene, VIEWCUBE_PX};
|
||||
use crate::app::Message;
|
||||
use acadrust::Handle;
|
||||
use iced::widget::shader;
|
||||
use iced::{mouse, Event, Rectangle};
|
||||
@@ -70,7 +71,7 @@ impl<'a> PaperViewportPane<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPane<'a> {
|
||||
impl<'a> shader::Program<Message> for PaperViewportPane<'a> {
|
||||
type State = CameraState;
|
||||
type Primitive = PaperViewportPrimitive;
|
||||
|
||||
@@ -90,9 +91,30 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPan
|
||||
event: &Event,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Option<iced::widget::Action<Msg>> {
|
||||
) -> Option<iced::widget::Action<Message>> {
|
||||
self.scene.update_viewcube_state(state, bounds, cursor);
|
||||
let _ = event;
|
||||
// A left click on the ViewCube snaps the viewport's view. The
|
||||
// hit-test runs in bounds-relative coordinates so it matches the
|
||||
// gizmo drawn in this viewport's top-right corner. Clicks outside
|
||||
// the cube return None, so they fall through to the paper sheet
|
||||
// for normal selection / drawing.
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event {
|
||||
if let Some(pos) = cursor.position_in(bounds) {
|
||||
let rot = self.scene.active_view_rotation_mat();
|
||||
if let Some(region) = super::hit_test(
|
||||
pos.x,
|
||||
pos.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
rot,
|
||||
VIEWCUBE_PX,
|
||||
) {
|
||||
return Some(iced::widget::Action::publish(Message::ViewCubeSnap(
|
||||
region,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -127,7 +149,7 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
|
||||
),
|
||||
ViewportPaneMode::Paper { handle } => {
|
||||
self.scene
|
||||
.build_viewport_primitive(*handle, state.hover_region, bounds)
|
||||
.build_viewport_primitive(*handle, state.hover_region, bounds, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user