graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: the dialog's spin rows land as draw-time values too
Grid Thickness, Origin Size, Point Marker Size and Camera Pivot Size were
the four dialog rows still going through `apply_setting` on every motion
of a drag — a graph evaluation and a state.kdl write per pointer event —
when each of them feeds exactly one small guide mesh (the grid, the three
axes, the overlay markers, the pivot).
`land_draw_time_setting` gains the grid and origin arms, and
`land_dialog_slider` lands a spin row's whole number over the row's unit
through it; the Camera Pivot Size row, whose owner is the active camera,
writes as `setting_write` always has and re-bakes the pivot. Same
save-on-release contract as the slider rows. The drag test covers all
four: the field, the re-baked mesh, an unmoved geometry version, and the
file written on a single landing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CLAUDE.md | 5 +++--
src/app.rs | 18 +++++++++++++++---
src/dialog.rs | 50 +++++++++++++++++++++++++++++++-------------------
src/main.rs | 25 ++++++++++++++++++++++---
4 files changed, 71 insertions(+), 27 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 51dc5c5..6359777 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1933,8 +1933,9 @@ selection stays where it was:
params pane's pickers), a path-tracer restart and a synchronous file
write, per pointer event, for six values the graph never reads — which
is what made the dialog's sliders drag behind the pointer while the
- menu's did not. A row the landing does not know (a spin row) still
- takes the full apply, whose regenerate it needs;
+ menu's did not. The spin rows (Grid Thickness, Origin Size, Point
+ Marker Size, Camera Pivot Size) land the same way, their whole number
+ over the row's unit, each re-baking only the guide mesh that reads it;
`a_dialog_slider_drag_lands_without_re_evaluating_the_graph` pins all
of it. The **zoom row**
(`ZOOM_ROW_ID`, only while the network pane is focused, since zoom is that
diff --git a/src/app.rs b/src/app.rs
index 9c2b59c..4a4919e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -4791,9 +4791,11 @@ impl State {
/// Land a DRAW-TIME display setting by its `DesignSettings` field key —
/// the one landing behind the viewport menu's sliders AND the dialog's
- /// (`land_dialog_slider`), so the two cannot disagree about a clamp or
- /// about which marker mesh a size feeds. Returns false for a key that is
- /// not one of these, which the dialog takes as "run the full apply".
+ /// slider and spin rows (`land_dialog_slider`), so the two cannot
+ /// disagree about a clamp or about which mesh a size feeds. The three
+ /// guide sizes re-bake their own small mesh (a grid, three axes, a
+ /// pivot) and nothing else. Returns false for a key that is not one of
+ /// these, which the dialog takes as "run the full apply".
///
/// Until 2026-09-28 the dialog's sliders went through `apply_setting` on
/// every motion of a drag: a whole graph evaluation, a second one for
@@ -4822,6 +4824,16 @@ impl State {
self.pull_arrow_scale = v.clamp(0.25, 10.0);
self.rebuild_pull_arrow_verts();
}
+ // The spin rows' ranges, in world units (the rows read in
+ // thousandths and tenths).
+ "grid_thickness" => {
+ self.grid_thickness = v.clamp(0.002, 0.2);
+ self.update_grid_geometry();
+ }
+ "origin_size" => {
+ self.origin_size = v.clamp(0.1, 5.0);
+ self.update_origin_geometry();
+ }
_ => return false,
}
self.viewport_dirty = true;
diff --git a/src/dialog.rs b/src/dialog.rs
index 9ae7c0d..5a6f548 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -2082,30 +2082,42 @@ impl State {
/// A slider row's value arriving from a drag, a wheel or an arrow: the
/// zoom row zooms, a setting row writes its setting.
///
- /// A slider row is a draw-time value, so it lands through the viewport
- /// menu's own `land_draw_time_setting` — the field, the one mesh it
- /// feeds, a redraw — and the row re-reads in place. During a drag this
- /// runs on every motion, so state.kdl is written on the RELEASE
- /// (`dialog_mouse_input`) rather than here; a wheel notch or an arrow
- /// key is a single landing and saves at once, as the menu's wheel does.
- /// A row the landing does not know falls through to `apply_setting`,
- /// whose full regenerate pass is what a spin row like Grid Thickness
- /// needs.
+ /// A slider or spin row is a draw-time value, so it lands through the
+ /// viewport menu's own `land_draw_time_setting` — the field, the one
+ /// mesh it feeds, a redraw — and the row re-reads in place. During a
+ /// drag this runs on every motion, so state.kdl is written on the
+ /// RELEASE (`dialog_mouse_input`) rather than here; a wheel notch or an
+ /// arrow key is a single landing and saves at once, as the menu's wheel
+ /// does. A spin row lands its whole number over the row's unit; the
+ /// Camera Pivot Size row, whose owner is the active camera, writes as
+ /// `setting_write` always has and re-bakes the pivot. A row the landing
+ /// does not know falls through to `apply_setting`.
pub(crate) fn land_dialog_slider(&mut self, id: &str, v: f32) {
if id == ZOOM_ROW_ID {
self.set_zoom_percent(v);
} else if let Some(s) = setting_of_row(id) {
- if let (Owner::Field(key), Ctl::Slider { dec, .. }) = (s.owner, s.ctl) {
- // Rounded as the row shows it, so the field holds the value
- // the readout names rather than the pointer's raw fraction.
- let shown: f32 = format!("{:.*}", dec, v).parse().unwrap_or(v);
- if self.land_draw_time_setting(key, shown) {
- self.refresh_dialog_controls();
- if !self.slots.dialog.slider_dragging() {
- self.save_settings();
- }
- return;
+ let landed = match (s.owner, s.ctl) {
+ (Owner::Field(key), Ctl::Slider { dec, .. }) => {
+ // Rounded as the row shows it, so the field holds the
+ // value the readout names rather than the pointer's raw
+ // fraction.
+ let shown: f32 = format!("{:.*}", dec, v).parse().unwrap_or(v);
+ self.land_draw_time_setting(key, shown)
+ }
+ (Owner::Field(key), Ctl::Spin { unit, .. }) => self.land_draw_time_setting(key, v.round() / unit),
+ (Owner::ActiveCamera(_), Ctl::Spin { .. }) => {
+ self.setting_write(s, &(v.round() as i64).to_string());
+ self.update_pivot_geometry();
+ true
}
+ _ => false,
+ };
+ if landed {
+ self.refresh_dialog_controls();
+ if !self.slots.dialog.slider_dragging() {
+ self.save_settings();
+ }
+ return;
}
let value = match s.ctl {
Ctl::Slider { dec, .. } => format!("{:.*}", dec, v),
diff --git a/src/main.rs b/src/main.rs
index 78814cc..8ca38c7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10964,14 +10964,33 @@ mod tests {
assert!((state.geo_opacity - 1.0).abs() < 1e-6, "clamped");
assert_eq!(state.rt_geometry_version, version);
- // Point Size re-sizes the group markers from their kept members,
- // and a spin row still takes the full regenerate pass.
+ // Point Size re-sizes the group markers from their kept members.
state.land_dialog_slider(&setting_row_id("Point Size"), 0.05);
assert!((state.point_size - 0.05).abs() < 1e-6);
assert!((state.last_group_marker_size - state.group_marker_size()).abs() < 1e-6);
assert_eq!(state.rt_geometry_version, version);
+
+ // The spin rows land the same way: a whole number over the row's
+ // unit, re-baking only the guide mesh that reads it.
+ state.pending_grid = None;
+ state.pending_origin = None;
+ state.pending_pivot = None;
state.land_dialog_slider(&setting_row_id("Grid Thickness"), 40.0);
- assert!(state.rt_geometry_version > version, "a spin row regenerates");
+ assert!((state.grid_thickness - 0.04).abs() < 1e-6, "{}", state.grid_thickness);
+ assert!(state.pending_grid.is_some(), "the grid re-baked");
+ state.land_dialog_slider(&setting_row_id("Origin Size"), 25.0);
+ assert!((state.origin_size - 2.5).abs() < 1e-6, "{}", state.origin_size);
+ assert!(state.pending_origin.is_some(), "the origin re-baked");
+ state.land_dialog_slider(&setting_row_id("Point Marker Size"), 50.0);
+ assert!((state.point_marker_size - 0.05).abs() < 1e-6, "{}", state.point_marker_size);
+ state.land_dialog_slider(&setting_row_id("Camera Pivot Size"), 20.0);
+ assert!((state.camera_pivot_size - 2.0).abs() < 1e-6, "{}", state.camera_pivot_size);
+ assert!(state.pending_pivot.is_some(), "the pivot re-baked");
+ assert_eq!(state.rt_geometry_version, version, "a spin row re-evaluated the graph");
+ assert!((saved(&path) - 1.0).abs() < 1e-3, "the file follows every single landing");
+ let kdl = fs::read_to_string(&path).expect("a settings file");
+ let back = crate::app::DesignSettings::from_kdl_str(&kdl).viewport;
+ assert!((back.grid_thickness - 0.04).abs() < 1e-6 && (back.origin_size - 2.5).abs() < 1e-6, "{kdl}");
}
/// A right press is the dialog's while it is open: inside the plate it is