git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit42bb569c3605f5d1edecb7605f13678171ba505f
parentaa8ea20984
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 21:04
feat(viewport): remove the scale readout above the playbar

The viewer-state line takes the bottom row it stood above. View 1:1 and
view_scale_ratio stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md     |  2 +-
 src/app.rs    |  4 ++--
 src/main.rs   |  2 +-
 src/render.rs | 58 ++++++++++------------------------------------------------
 4 files changed, 14 insertions(+), 52 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index c6b2f31..3955a8d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -3562,7 +3562,7 @@ any case. `a_point_number_under_a_plate_is_not_drawn` is the test.
 is when the frame is painted** (`State::refresh_scene_view`, at the top of
 `collect_display_list`, since 2026-09-29). The runner paints the 2D frame
 and THEN stages the scene, and the stage pass was the one place
-`last_scene_mvp`, the pane's rect and the eye were set — so the numbers,
+`last_scene_mvp`, the pane's rect and the eye were set — so the numbers
 and a viewer state's handles were placed by the camera
 of the frame before. They trailed the geometry and its markers, which
 are meshes drawn by the frame's own matrix, by a frame whenever the
diff --git a/src/app.rs b/src/app.rs
index 1a301de..5ee0378 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2182,7 +2182,7 @@ pub struct ViewportSettings {
     #[serde(default = "default_point_marker_color")]
     pub point_marker_color: [f32; 3],
     /// What one world unit IS (mm / cm / m / in). A DECLARATION — geometry
-    /// never converts; it feeds the scale readout and `View 1:1`.
+    /// never converts; it feeds `View 1:1`.
     #[serde(default = "default_world_unit")]
     pub world_unit: String,
     /// The path-traced preview.
@@ -4030,7 +4030,7 @@ impl State {
     /// The 2D frame is painted BEFORE the stage pass stages the scene, and
     /// until 2026-09-29 the stage pass was the one place these were set: so
     /// everything the 2D frame draws over the scene — the point, primitive
-    /// and vertex numbers, a viewer state's handles, the scale readout —
+    /// and vertex numbers, a viewer state's handles —
     /// was placed by the camera of the frame BEFORE, and trailed the
     /// geometry and its markers by a frame whenever the camera moved. When
     /// it stopped they stood a frame's move off their points until
diff --git a/src/main.rs b/src/main.rs
index 70def00..0f10b3e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2137,7 +2137,7 @@ mod tests {
             let (_, y, _, h) = state.positions[idx];
             assert_eq!(y + h, pb.1 - 18.0, "the {idx} plate stops a gap above it");
         }
-        assert_eq!(state.scene_text_floor(900.0), pb.1, "the scale readout stands on it");
+        assert_eq!(state.scene_text_floor(900.0), pb.1, "the viewport's bottom text stands on it");
         state.execute_menu_action("Show Playbar Pane");
         assert!(!state.show_playbar);
         assert_eq!(state.scene_text_floor(900.0), 900.0);
diff --git a/src/render.rs b/src/render.rs
index 4806f8f..89c17ee 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -256,7 +256,7 @@ impl State {
             // The dialog is painted after the overlay passes below, not in the
             // walk. A high z_index is not enough: `append_frame_text` and the
             // viewport overlays run AFTER the whole walk, so the graph's node
-            // labels and the scale readout drew straight over a dialog that
+            // labels drew straight over a dialog that
             // had already covered them.
             if i == DIALOG_IDX {
                 continue;
@@ -269,7 +269,6 @@ impl State {
         self.append_context_border(&mut pc);
         self.append_frame_text(&mut pc);
         self.append_point_numbers(&mut pc);
-        self.append_scale_readout(&mut pc);
         self.append_viewer_state_overlay(&mut pc);
         self.append_popovers(&mut pc);
         // Above every pane AND every overlay text pass, below only the context
@@ -1054,7 +1053,7 @@ impl State {
     ///
     /// Out of the widget walk entirely, because the walk is not the end of the
     /// frame — `append_frame_text` and the viewport overlays follow it, and
-    /// they drew the graph's node labels and the scale readout straight over
+    /// they drew the graph's node labels straight over
     /// a dialog whose z_index had already put it on top of the same panes.
     /// Only the context menu goes above this, and it can be opened from inside
     /// the dialog.
@@ -1180,50 +1179,14 @@ impl State {
         out
     }
 
-    /// The view's scale on the pivot plane, bottom-left of the pane: `1:2.3`
-    /// (the world shown at less than true size), `2.3:1` (magnified), or
-    /// `1:1`, with what one world unit is and how long it shows. Marked when
-    /// the display metric is only assumed — then the millimetres are the
-    /// CSS 96 ppi guess, not a measurement.
-    fn append_scale_readout(&self, pc: &mut PaintCtx) {
-        if !self.show_viewport {
-            return;
-        }
-        let (vx, vy, vw, vh) = self.last_scene_view_rect;
-        if vw <= 0.0 || vh <= 0.0 {
-            return;
-        }
-        let r = self.view_scale_ratio();
-        if !r.is_finite() || r <= 0.0 {
-            return;
-        }
-        let ratio = if (r - 1.0).abs() < 0.01 {
-            "1:1".to_string()
-        } else if r > 1.0 {
-            format!("1:{}", cce_ui::units::fmt_num((r * 100.0).round() / 100.0))
-        } else {
-            format!("{}:1", cce_ui::units::fmt_num((100.0 / r).round() / 100.0))
-        };
-        let m = cce_ui::units::metric();
-        let shown_mm = self.world_unit_mm() / r;
-        let mut text = format!("{ratio}  ·  1 {} = {} mm on screen", self.world_unit.suffix(), cce_ui::units::fmt_num((shown_mm * 100.0).round() / 100.0));
-        if !m.is_real() {
-            text.push_str("  ·  metric assumed");
-        }
-        pc.clip(rect(vx, vy, vw, vh), |pc| {
-            pc.text(text, vx + 8.0, self.scene_text_floor(vy + vh) - 16.0, 10.0, [0xaa, 0xaa, 0xbb]);
-        });
-    }
-
     /// The curve viewer state's handles: each control point projected
     /// through the cached scene mvp (like the point numbers above), drawn as
     /// a ringed dot with its index, the control cage as faint segments
     /// between them. Selected point draws larger and brighter.
     /// Where the viewport's bottom-anchored text stands: the viewport's
     /// bottom, or the playbar's top while it is shown — the playbar is
-    /// attached to the window's bottom edge, over the scene, and the scale
-    /// readout and the viewer-state line would otherwise sit on its
-    /// transport.
+    /// attached to the window's bottom edge, over the scene, and the
+    /// viewer-state line would otherwise sit on its transport.
     pub(crate) fn scene_text_floor(&self, bottom: f32) -> f32 {
         let (_, py, pw, ph) = self.positions[crate::slots::PLAYBAR_IDX];
         if self.show_playbar && pw > 0.0 && ph > 0.0 {
@@ -1281,12 +1244,11 @@ impl State {
             }
         });
 
-        // The HUD sits one line ABOVE the scale readout, sharing its left
-        // margin. Not at the top: the viewport is full-bleed and the pane
-        // plates float over its top edge, so a mode line there lands under the
-        // collapsed stubs and their titles read through it. Not at the very
-        // bottom either — that row belongs to the scale readout, and two
-        // sentences on one line read as one garbled sentence.
+        // The HUD sits at the viewport's bottom left. Not at the top: the
+        // viewport is full-bleed and the pane plates float over its top edge,
+        // so a mode line there lands under the collapsed stubs and their
+        // titles read through it. (It stood a line above the scale readout
+        // until that was removed on 2026-10-06.)
         //
         // It exists because a viewer state changes what every click does and
         // snapping silently changes what a drag does. A mode you cannot see is
@@ -1295,7 +1257,7 @@ impl State {
         let hud = tool.hud();
         let size = 11.0;
         let pad = 5.0;
-        let y = self.scene_text_floor(vy + vh) - 16.0 - (size + pad * 2.0) - 4.0;
+        let y = self.scene_text_floor(vy + vh) - (size + pad * 2.0) - 8.0;
         let width = (hud.chars().count() as f32 * size * 0.52 + pad * 2.0).min(vw - 16.0);
         pc.clip(rect(vx, vy, vw, vh), |pc| {
             pc.quad(rect(vx + 8.0 - pad, y, width, size + pad * 2.0), [0.0, 0.0, 0.0, 0.55]);