LR-CROP-STRAIGHTEN: the Angle field applies a typed angle on Return

The field applied every keystroke: an undo step and a render per key,
and half-typed angles (-3° on the way to -3.25°). It now applies on
Return or when focus leaves, as one undo step, and ignores changes that
leave the angle as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-09 12:10:50 -04:00
co-authored by Claude Opus 5.5
parent 676e8c01ce
commit 7e109e880f
2 changed files with 34 additions and 2 deletions
+7 -2
View File
@@ -196,14 +196,19 @@ fn crop(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
.clamp_existing_to_range(false)
.speed(0.05)
.fixed_decimals(2)
.suffix("°"),
.suffix("°")
// a typed angle applies on Return (or leaving the field), not keystroke by
// keystroke: no half-typed angles, one undo step
.update_while_editing(false),
);
crate::widgets::register(ui.ctx(), "cropAngleField", r.rect);
// a drag on the field is one undo step, like the slider's
if r.drag_started() {
let _ = app.run("develop.beginInteraction", json!({"label": "Straighten"}));
}
if r.changed() && angle.is_finite() {
// egui reports a change while text is typed even when the value isn't updated yet:
// apply only a new angle
if r.changed() && angle.is_finite() && angle != d.crop.geometry.angle {
let _ = app.run("crop.straighten", json!({"angle": (angle.clamp(spec.min, spec.max) * 100.0).round() / 100.0}));
}
if r.drag_stopped() {
+27
View File
@@ -179,6 +179,33 @@ fn dragging_the_angle_field_is_one_undo_step() {
assert_eq!(h.app.session.undo.len(), undo + 1, "one step for the whole drag");
}
/// Typing into the Angle field one key at a time applies the angle once, on Return: no undo step
/// or render per keystroke, no half-typed angles (-3° on the way to -3.25°).
#[test]
fn typing_in_the_angle_field_applies_on_return() {
let mut h = crop_tool();
let r = h.request("ui.clickWidget", json!({"id": "cropAngleField"}), T);
assert_eq!(r["ok"], true, "{r}");
h.settle(SETTLE);
for key in ["a", "Backspace"] {
let r = h.request("ui.key", json!({"key": key, "cmd": key == "a"}), T);
assert_eq!(r["ok"], true, "{r}");
}
h.settle(SETTLE);
let (before, undo) = (angle(&h), h.app.session.undo.len());
for ch in ["-", "3", ".", "2", "5"] {
let r = h.request("ui.text", json!({"text": ch}), T);
assert_eq!(r["ok"], true, "{r}");
h.settle(SETTLE);
}
assert_eq!((angle(&h), h.app.session.undo.len()), (before, undo), "nothing applied while typing");
let r = h.request("ui.key", json!({"key": "Enter"}), T);
assert_eq!(r["ok"], true, "{r}");
h.settle(SETTLE);
assert!((angle(&h) + 3.25).abs() < 1e-9, "applied on Return: {}", angle(&h));
assert_eq!(h.app.session.undo.len(), undo + 1, "as one undo step");
}
/// Rotating in the margin beside a narrow photo keeps the readout next to the pointer: it is kept
/// on the canvas, not pinned to the photo's edge.
#[test]