graphic design tool
git clone https://git.lucas.co/cce-designer.git
Merge remote-tracking branch 'origin/main' into claude/2d-features
CLAUDE.md | 77 ++++++++++++--
src/app.rs | 307 +++++++++++++++++++++++++++++++++++++++++++++++---------
src/command.rs | 3 +
src/dialog.rs | 3 +
src/geometry.rs | 125 +++++++++++++++++++++++
src/main.rs | 227 +++++++++++++++++++++++++++++++++++------
src/render.rs | 129 +++++++++++++++++++++---
src/shortcut.rs | 6 ++
8 files changed, 778 insertions(+), 99 deletions(-)
diff --cc src/app.rs
index c327670,9b66c1c..c50a355
--- a/src/app.rs
+++ b/src/app.rs
@@@ -5224,45 -5298,71 +5317,77 @@@ impl State
actions.push(a);
};
let sep = ViewportMenuAction::Separator;
+ let toggle = |options: &mut Vec<String>, actions: &mut Vec<ViewportMenuAction>, id: &'static str| {
+ let on = self.command_toggle_state(id).unwrap_or(false);
+ options.push(format!("{} {}", mark(on), label(id, id)));
+ actions.push(ViewportMenuAction::Command(id));
+ };
+
+ match page {
+ Some(ViewportMenuPage::Style) => {
+ row(&mut options, &mut actions, format!("‹ {}", ViewportMenuPage::Style.label()), ViewportMenuAction::Back);
+
+ // Wireframe.
+ row(&mut options, &mut actions, "-".into(), sep);
+ toggle(&mut options, &mut actions, "toggle_wireframe");
+ row(&mut options, &mut actions, "Wire Thickness".into(), ViewportMenuAction::WireThicknessSlider);
+ row(&mut options, &mut actions, "Wire Opacity".into(), ViewportMenuAction::WireOpacitySlider);
+
+ // Surface.
+ row(&mut options, &mut actions, "-".into(), sep);
+ row(&mut options, &mut actions, format!("{} Flat Shading", mark(!self.smooth_shading)), ViewportMenuAction::Shading(false));
+ row(&mut options, &mut actions, format!("{} Smooth Shading", mark(self.smooth_shading)), ViewportMenuAction::Shading(true));
+ row(&mut options, &mut actions, "Opacity".into(), ViewportMenuAction::OpacitySlider);
+ toggle(&mut options, &mut actions, "toggle_show_occluded");
+ return (options, actions);
+ }
+ Some(ViewportMenuPage::Markers) => {
+ row(&mut options, &mut actions, format!("‹ {}", ViewportMenuPage::Markers.label()), ViewportMenuAction::Back);
+
+ // The Render points, the group markers sized off them, and
+ // the pull arrows' length, the other selection feedback.
+ row(&mut options, &mut actions, "-".into(), sep);
+ toggle(&mut options, &mut actions, "toggle_render_points");
+ row(&mut options, &mut actions, "Point Size".into(), ViewportMenuAction::PointSizeSlider);
+ row(&mut options, &mut actions, "Group Marker Scale".into(), ViewportMenuAction::GroupMarkerScaleSlider);
+ row(&mut options, &mut actions, "Pull Arrow Scale".into(), ViewportMenuAction::PullArrowScaleSlider);
+
+ // The overlays, a class at a time: points, primitives,
+ // vertices.
+ row(&mut options, &mut actions, "-".into(), sep);
+ toggle(&mut options, &mut actions, "toggle_point_markers");
+ row(&mut options, &mut actions, "Point Marker Size".into(), ViewportMenuAction::PointMarkerSizeSlider);
+ toggle(&mut options, &mut actions, "toggle_point_numbers");
+ toggle(&mut options, &mut actions, "toggle_point_normals");
+ row(&mut options, &mut actions, "-".into(), sep);
+ toggle(&mut options, &mut actions, "toggle_prim_numbers");
+ toggle(&mut options, &mut actions, "toggle_prim_normals");
+ row(&mut options, &mut actions, "-".into(), sep);
+ toggle(&mut options, &mut actions, "toggle_vertex_numbers");
+ return (options, actions);
+ }
+ None => {}
+ }
+
+ row(&mut options, &mut actions, "Frame All".into(), ViewportMenuAction::FrameAll);
+ row(&mut options, &mut actions, "View 1:1".into(), ViewportMenuAction::OneToOne);
+ // The image's two camera rows, under the scene's, while one shows.
+ if self.page_shown.is_some() {
+ row(&mut options, &mut actions, label("frame_image", "Frame Image").to_string(), ViewportMenuAction::Command("frame_image"));
+ row(&mut options, &mut actions, label("view_image_pixels", "View Image Pixels 1:1").to_string(), ViewportMenuAction::Command("view_image_pixels"));
+ }
+
// Guides: the scene furniture that is not the geometry.
row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} {}", mark(self.viewport().show_grid), label("toggle_grid", "Show Grid")), ViewportMenuAction::Command("toggle_grid"));
- row(&mut options, &mut actions, format!("{} {}", mark(self.viewport().show_origin), label("toggle_origin", "Show Origin")), ViewportMenuAction::Command("toggle_origin"));
-
- // Wireframe.
- row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} {}", mark(self.wireframe), label("toggle_wireframe", "Show Wireframe")), ViewportMenuAction::Command("toggle_wireframe"));
- row(&mut options, &mut actions, "Wire Thickness".into(), ViewportMenuAction::WireThicknessSlider);
- row(&mut options, &mut actions, "Wire Opacity".into(), ViewportMenuAction::WireOpacitySlider);
-
- // Points: the Render points, the group markers sized off them, and
- // the pull arrows' length, the other selection feedback beside them.
- row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} {}", mark(self.render_points), label("toggle_render_points", "Show Points")), ViewportMenuAction::Command("toggle_render_points"));
- row(&mut options, &mut actions, "Point Size".into(), ViewportMenuAction::PointSizeSlider);
- row(&mut options, &mut actions, "Group Marker Scale".into(), ViewportMenuAction::GroupMarkerScaleSlider);
- row(&mut options, &mut actions, "Pull Arrow Scale".into(), ViewportMenuAction::PullArrowScaleSlider);
+ toggle(&mut options, &mut actions, "toggle_grid");
+ toggle(&mut options, &mut actions, "toggle_origin");
- // Overlays: the three annotations over the scene's points.
+ // The display settings, a page each.
row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} {}", mark(self.show_point_markers), label("toggle_point_markers", "Show Point Markers")), ViewportMenuAction::Command("toggle_point_markers"));
- row(&mut options, &mut actions, "Point Marker Size".into(), ViewportMenuAction::PointMarkerSizeSlider);
- row(&mut options, &mut actions, format!("{} {}", mark(self.show_point_numbers), label("toggle_point_numbers", "Show Point Numbers")), ViewportMenuAction::Command("toggle_point_numbers"));
- row(&mut options, &mut actions, format!("{} {}", mark(self.show_point_normals), label("toggle_point_normals", "Show Point Normals")), ViewportMenuAction::Command("toggle_point_normals"));
-
- // Surface.
- row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} Flat Shading", mark(!self.smooth_shading)), ViewportMenuAction::Shading(false));
- row(&mut options, &mut actions, format!("{} Smooth Shading", mark(self.smooth_shading)), ViewportMenuAction::Shading(true));
- row(&mut options, &mut actions, "Opacity".into(), ViewportMenuAction::OpacitySlider);
- row(&mut options, &mut actions, format!("{} {}", mark(self.show_occluded), label("toggle_show_occluded", "Show Occluded")), ViewportMenuAction::Command("toggle_show_occluded"));
+ for page in [ViewportMenuPage::Style, ViewportMenuPage::Markers] {
+ row(&mut options, &mut actions, format!("{} ›", page.label()), ViewportMenuAction::Submenu(page));
+ }
// The viewport's editor binding, as a radio group: follow the active
// editor, or pin to one. Pin rows appear only while a second editor
@@@ -10747,12 -10947,30 +10951,35 @@@ pub(crate) fn geometry_to_spreadsheet_d
if self.overlay_point_count > 0 {
draws.push(SceneDraw { mesh: meshes.overlay_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
}
+ // The page the level shows stands in the scene as an
+ // image: after the furniture and the markers, which are
+ // opaque and may show through it, and before the
+ // geometry, whose fill may be translucent over it.
+ let image_slot = draws.len() as u32;
if self.vertex_count_spheres > 0 {
+ // The line annotations go UNDER the geometry, as
+ // the markers above do, and write depth (the wire
+ // draw's `see_through` selects the depth-writing
+ // line pipeline): what is nearer then blends over
+ // them, so a whisker behind a translucent face or
+ // wire is dimmed by it, and what is farther fails
+ // the test and leaves them whole. Drawn after the
+ // fill they were hidden outright behind one that
+ // writes depth, and painted at full strength over
+ // the near faces of one that does not.
+ //
+ // Show Point Normals: thin cyan whiskers,
+ // width deliberately fixed (a chunky Wire Width is a
+ // wireframe styling choice, not a normals one).
+ if self.overlay_normal_count > 0 {
+ draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: true });
+ }
+ // Pull arrows: selection feedback, like the group
+ // markers — full opacity, a little heavier than the
+ // whiskers.
+ if self.pull_arrow_count > 0 {
+ draws.push(SceneDraw { mesh: meshes.pull_arrows, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 2.0, wire_base_width: 0.0, prelit: false, see_through: true });
+ }
// With wires coming, the fill is pushed back by its
// slope-scaled offset so the lattice reads solid.
let base = if self.wireframe { self.wire_width } else { 0.0 };
@@@ -10789,29 -11007,8 +11016,17 @@@
draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: wire_alpha, line_width: self.wire_width, wire_base_width: 0.0, prelit: false, see_through });
}
draws.extend(fill);
- // Show Point Normals: thin cyan whiskers,
- // width deliberately fixed (a chunky Wire Width is a
- // wireframe styling choice, not a normals one).
- if self.overlay_normal_count > 0 {
- draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
- }
- // Pull arrows: selection feedback, like the group
- // markers — full opacity, a little heavier than the
- // whiskers so they read over a wireframe.
- if self.pull_arrow_count > 0 {
- draws.push(SceneDraw { mesh: meshes.pull_arrows, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 2.0, wire_base_width: 0.0, prelit: false, see_through: false });
- }
}
renderer.stage_scene((sx, sy, cw, ch), draws);
+ if let (Some(image), Some(shown)) = (self.page_image, &self.page_shown) {
+ renderer.stage_scene_images(vec![cce_ui::vk::SceneImage {
+ image,
+ corners: shown.world_corners(self.world_unit_mm()),
+ mvp,
+ opacity: 1.0,
+ before: image_slot,
+ }]);
+ }
}
// Update viewport cache