From 4ff1d9761d43b80372161394bc51bdaa2880ca51 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 12:59:15 +0000 Subject: [PATCH] Batch 30: a selected slider keeps only its arrow keys, so shortcuts work after a slider click (#884); tests follow the merged dialog and panel state #884 focuses a develop slider on click so the wheel and arrows adjust it, but a focused widget owned the whole keyboard, so after touching a slider no shortcut fired (O for the mask overlay, caught by mask_adjustments_hide_overlay_without_disabling_the_mask). Now a selected slider owns the arrow keys only. The wheel test opens the Edit panel explicitly (the demo may start on it, and panel.edit then toggles it shut), and the Export dialog snapshot is 546 px wide since #833's nine watermark positions. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4 --- crates/ui-egui/src/shortcuts.rs | 4 ++++ crates/ui-egui/src/tests_dialog_fit.rs | 5 +++-- crates/ui-egui/src/tests_masking.rs | 3 +++ crates/ui-egui/src/widgets.rs | 12 ++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 59384199..129fb1d9 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -308,6 +308,10 @@ fn keyboard_owned(app: &LightcraftApp, ctx: &egui::Context) -> bool { return ctx.input(|input| input.key_pressed(Key::Escape)) && app.widgets.iter().any(|(widget, _)| widget.starts_with("sliderField:") && crate::text_field::was_editing(ctx, widget)); } + // a selected slider keeps its arrow keys; everything else is a shortcut + if crate::widgets::slider_has_focus(ctx) { + return ctx.input(|input| [Key::ArrowLeft, Key::ArrowRight, Key::ArrowUp, Key::ArrowDown].iter().any(|key| input.key_pressed(*key))); + } let chrome = app .ui .docking diff --git a/crates/ui-egui/src/tests_dialog_fit.rs b/crates/ui-egui/src/tests_dialog_fit.rs index d2d69c05..5c4f1a83 100644 --- a/crates/ui-egui/src/tests_dialog_fit.rs +++ b/crates/ui-egui/src/tests_dialog_fit.rs @@ -93,9 +93,10 @@ fn export_dialog_is_unchanged_in_a_tall_window() { let h = export_dialog([1600.0, 1000.0]); assert_fits(&h, "Export at 1600×1000"); let dialog = rect(&h, "dialog:window"); - // (846 × 500.6 before #781: the whole dialog, nothing scrolled away) + // (846 tall as before #781: the whole dialog, nothing scrolled away; 546 wide since the + // watermark's nine positions, #833) assert!((dialog.height() - 846.0).abs() <= 1.0, "{dialog:?}"); - assert!((dialog.width() - 500.6).abs() <= 1.0, "{dialog:?}"); + assert!((dialog.width() - 546.0).abs() <= 1.0, "{dialog:?}"); } // Scenario: other long dialogs fit a short window too, with their buttons on screen diff --git a/crates/ui-egui/src/tests_masking.rs b/crates/ui-egui/src/tests_masking.rs index e5edf3c7..8024c62c 100644 --- a/crates/ui-egui/src/tests_masking.rs +++ b/crates/ui-egui/src/tests_masking.rs @@ -1337,6 +1337,9 @@ fn color_range_pick_button_samples_the_photo() { #[test] fn a_selected_develop_slider_accepts_wheel_nudges() { let mut h = detail("panel.edit"); + // the demo may open on the Edit panel already, which `panel.edit` then toggles shut + let r = h.request("ui.set", json!({"right": "edit"}), T); + assert_eq!(r["ok"], true, "{r}"); h.settle(SETTLE); let name = "slider:light.exposure"; let panel = h.app.widgets.iter().find(|(id, _)| id == "panel:right_panel").unwrap().1; diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index ea187591..0872b2ec 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -187,6 +187,7 @@ pub fn slider(ui: &mut Ui, spec: &ControlSpec, value: f64, enabled: bool, label_ // opt-in so hovering an unfocused slider still scrolls the containing panel. if enabled && (resp.clicked() || resp.drag_started()) { resp.request_focus(); + ui.data_mut(|d| d.insert_temp(focused_slider_key(), id)); } // screen readers: a slider named after its control, with its value let label_text = crate::i18n::tr(label_override.unwrap_or(spec.label)).to_string(); @@ -726,3 +727,14 @@ pub(crate) fn elide_head(text: &str, max: f32, width: impl Fn(&str) -> f32) -> S #[cfg(test)] #[path = "widgets/tests_numeric_entry.rs"] mod numeric_entry_tests; + +fn focused_slider_key() -> egui::Id { + egui::Id::new("lightcraft-focused-slider") +} + +/// Whether the widget with keyboard focus is a develop slider a click selected. It owns only the +/// arrow keys (and the wheel): every other shortcut still works, as in Lightroom after a drag. +pub fn slider_has_focus(ctx: &egui::Context) -> bool { + let focused = ctx.memory(|m| m.focused()); + focused.is_some() && focused == ctx.data(|d| d.get_temp::(focused_slider_key())) +}