Effect Controls: double-clicking a property's name resets it (#3735)

* Effect Controls: double-clicking a property's name resets it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG

* Effect Controls: the property-name automation ID carries the effect index and is clipped to the view

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG

* test: the name double-click is one undo step and leaves the row in place

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
infinityespi
2026-11-01 15:15:07 -05:00
committed by GitHub
parent 3f89e7fdbb
commit dfbd8e24fc
2 changed files with 42 additions and 1 deletions
@@ -648,6 +648,20 @@ pub(crate) fn param_row(
x += 14.0;
let vx = r.min.x + (r.width() * 0.5).max(150.0);
row_label(ui, pos2(x, r.center().y), crate::i18n::t(pd.label), vx - 6.0 - x, Tokens::ui(12.0), t.text);
// double-clicking a property's name resets it, as the reset button at the row's end does (a
// click on the value itself starts typing, so the name is the double-click target)
if mask.is_none() {
let name = Rect::from_min_max(pos2(x, r.min.y), pos2(vx - 6.0, r.max.y));
// the instance index tells two copies of one effect apart; only the visible part of a row
// scrolled partly out of view is registered
let shown = name.intersect(ui.clip_rect());
if shown.is_positive() {
app.auto.add(&format!("effectControls.{}.{idx}.{}.name", e.effect, pkey), shown, crate::i18n::t(pd.label));
}
if row.double_clicked() && row.interact_pointer_pos().is_some_and(|p| name.contains(p)) {
actions.push(("effects.resetParam".into(), json!({"clip": clip.0, "effect": idx, "param": pd.id})));
}
}
let value = param.value_at(mt);
let mut vui = ui.new_child(
egui::UiBuilder::new()
+28 -1
View File
@@ -30,7 +30,8 @@ impl Driver {
let (tx, rx) = channel();
let app = FilmcraftApp::new(session).with_control(rx);
let shots = std::env::var_os("FILMCRAFT_UI_SHOTS").map(std::path::PathBuf::from);
let mut b = Harness::builder().with_size(egui::vec2(1600.0, 980.0)).with_max_steps(10_000);
// frames 1/60 s apart, so that two clicks make a double-click
let mut b = Harness::builder().with_size(egui::vec2(1600.0, 980.0)).with_step_dt(1.0 / 60.0).with_max_steps(10_000);
if shots.is_some() {
b = b.wgpu().with_pixels_per_point(1.0);
}
@@ -237,6 +238,32 @@ fn effect_controls_reset_button_keeps_keyframes() {
assert_eq!(value, 100.0);
}
/// Double-clicking a property's name resets it, as its reset button does; a single click doesn't.
#[test]
fn effect_controls_double_clicking_a_property_name_resets_it() {
let (mut d, clip) = Driver::demo();
d.exec("effects.setParam", json!({"clip": clip.id, "effect": "motion", "param": "scale", "value": 150.0}));
d.frames(2);
let [x, y, w, h] = d.rect("effectControls.motion.0.scale.name");
let (x, y) = (x + w * 0.3, y + h * 0.5);
d.ok("ui.click", json!({"x": x, "y": y}));
d.frames(3);
assert_eq!(d.scale(&clip).1, 150.0, "a single click leaves the value");
// half a second later, or egui counts the next clicks as a triple click
d.frames(30);
d.ok("ui.click", json!({"x": x, "y": y, "count": 2}));
d.frames(3);
let (keys, value) = d.scale(&clip);
assert!(keys.is_empty(), "{keys:?}");
assert_eq!(value, 100.0);
// the double-click neither folds nor renames anything: the row is still there
d.rect("effectControls.motion.0.scale.name");
// and the reset is one undo step
d.exec("edit.undo", json!({}));
d.frames(2);
assert_eq!(d.scale(&clip).1, 150.0, "one undo brings the value back");
}
/// Crop is not on a clip until it is used: its diamond applies the effect, then adds the keyframe.
#[test]
fn properties_diamond_applies_crop_first() {