graphic design tool
git clone https://git.lucas.co/cce-designer.git
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]);