graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: the viewport menu's display rows in three separated groups
Framing, then wireframe (switch, thickness), points (point size, point
marker size, group marker scale) and surface (flat/smooth shading,
opacity, Show Occluded), a separator apart; the editor pin rows follow as
before. the_viewport_menu_groups_its_display_rows holds the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 5 ++++-
src/app.rs | 63 ++++++++++++++++++++++++++++++-------------------------------
src/main.rs | 23 ++++++++++++++++++++++
3 files changed, 58 insertions(+), 33 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index f2e29db..2ed2cc4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1377,7 +1377,10 @@ multi-selection, so taking `Shift+L` now would have to be given back later.
### Display mode: the viewport menu, and smooth shading
The viewport's right-click menu carries the DISPLAY MODE under Frame All
-and View 1:1: the Show Wireframe switch (its registry command), **Flat
+and View 1:1, in three groups a separator apart — wireframe (switch,
+thickness), points (the three size sliders), surface (shading, opacity,
+Show Occluded); `the_viewport_menu_groups_its_display_rows` holds the
+order. The rows: the Show Wireframe switch (its registry command), **Flat
Shading / Smooth Shading** as a radio pair over `toggle_smooth_shading`,
a **Wire Thickness** slider under the wireframe switch (1–8 px by
half a pixel, the palette row's range), a **Point Size** slider (0–0.1
diff --git a/src/app.rs b/src/app.rs
index 5d4d24d..858c23f 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -4461,42 +4461,41 @@ impl State {
true
}
- /// The viewport menu's rows and what each does: framing, then the
- /// DISPLAY MODE — the wireframe switch and its thickness slider, the
- /// point size, point marker size and group marker scale sliders, flat or
- /// smooth shading as a radio pair, the polygon opacity slider and Show
- /// Occluded — then the editor pin. Split from the open so a test can
- /// read it. Marks are the ●/○ the pin rows and the network menu use.
+ /// The viewport menu's rows and what each does, in groups a separator
+ /// apart: framing; the WIREFRAME (its switch and thickness); the POINTS
+ /// (point size, point marker size, group marker scale); the SURFACE
+ /// (flat or smooth shading as a radio pair, the polygon opacity, Show
+ /// Occluded — the three that decide how the fill itself reads); then
+ /// the editor pin. Split from the open so a test can read it. Marks are
+ /// the ●/○ the pin rows and the network menu use.
pub(crate) fn viewport_menu_rows(&self) -> (Vec<String>, Vec<ViewportMenuAction>) {
let mut options = vec!["Frame All".to_string(), "View 1:1".to_string()];
let mut actions = vec![ViewportMenuAction::FrameAll, ViewportMenuAction::OneToOne];
let mark = |on: bool| if on { "●" } else { "○" };
-
- options.push("-".to_string());
- actions.push(ViewportMenuAction::Separator);
- let wire_label = crate::command::by_id("toggle_wireframe").map(|c| c.label).unwrap_or("Show Wireframe");
- options.push(format!("{} {wire_label}", mark(self.wireframe)));
- actions.push(ViewportMenuAction::Command("toggle_wireframe"));
- options.push("Wire Thickness".to_string());
- actions.push(ViewportMenuAction::WireThicknessSlider);
- options.push("Point Size".to_string());
- actions.push(ViewportMenuAction::PointSizeSlider);
- options.push("Point Marker Size".to_string());
- actions.push(ViewportMenuAction::PointMarkerSizeSlider);
- options.push("Group Marker Scale".to_string());
- actions.push(ViewportMenuAction::GroupMarkerScaleSlider);
- options.push(format!("{} Flat Shading", mark(!self.smooth_shading)));
- actions.push(ViewportMenuAction::Shading(false));
- options.push(format!("{} Smooth Shading", mark(self.smooth_shading)));
- actions.push(ViewportMenuAction::Shading(true));
-
- options.push("-".to_string());
- actions.push(ViewportMenuAction::Separator);
- options.push("Opacity".to_string());
- actions.push(ViewportMenuAction::OpacitySlider);
- let occluded_label = crate::command::by_id("toggle_show_occluded").map(|c| c.label).unwrap_or("Show Occluded");
- options.push(format!("{} {occluded_label}", mark(self.show_occluded)));
- actions.push(ViewportMenuAction::Command("toggle_show_occluded"));
+ let label = |id: &str, fallback: &'static str| crate::command::by_id(id).map(|c| c.label).unwrap_or(fallback);
+ let row = |options: &mut Vec<String>, actions: &mut Vec<ViewportMenuAction>, text: String, a: ViewportMenuAction| {
+ options.push(text);
+ actions.push(a);
+ };
+ let sep = ViewportMenuAction::Separator;
+
+ // 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);
+
+ // Points.
+ row(&mut options, &mut actions, "-".into(), sep);
+ row(&mut options, &mut actions, "Point Size".into(), ViewportMenuAction::PointSizeSlider);
+ row(&mut options, &mut actions, "Point Marker Size".into(), ViewportMenuAction::PointMarkerSizeSlider);
+ row(&mut options, &mut actions, "Group Marker Scale".into(), ViewportMenuAction::GroupMarkerScaleSlider);
+
+ // 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"));
// 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
diff --git a/src/main.rs b/src/main.rs
index 02cddf9..9095932 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1644,6 +1644,29 @@ mod tests {
context_menu::hide();
}
+ /// The viewport menu reads in groups a separator apart: framing, then
+ /// the wireframe, the points, and the surface (shading, opacity, Show
+ /// Occluded) — every display row in exactly one group.
+ #[test]
+ fn the_viewport_menu_groups_its_display_rows() {
+ use crate::app::ViewportMenuAction as A;
+ let state = State::new(false);
+ let (options, actions) = state.viewport_menu_rows();
+ let groups: Vec<Vec<A>> = actions
+ .split(|a| *a == A::Separator)
+ .map(|g| g.to_vec())
+ .collect();
+ assert_eq!(groups[0], vec![A::FrameAll, A::OneToOne]);
+ assert_eq!(groups[1], vec![A::Command("toggle_wireframe"), A::WireThicknessSlider]);
+ assert_eq!(groups[2], vec![A::PointSizeSlider, A::PointMarkerSizeSlider, A::GroupMarkerScaleSlider]);
+ assert_eq!(
+ groups[3],
+ vec![A::Shading(false), A::Shading(true), A::OpacitySlider, A::Command("toggle_show_occluded")]
+ );
+ assert_eq!(options.len(), actions.len());
+ assert!(options.iter().zip(&actions).all(|(o, a)| (o == "-") == (*a == A::Separator)), "separator rows line up");
+ }
+
/// Wire Thickness is a slider row right under Show Wireframe, over the
/// palette row's 1–8 px: the wheel steps half a pixel and saves, a press
/// on the band jumps, and the value is the live `wire_width` the wire