From 9d5993e9d1bba9f864f61f0e4c5e243514a84695 Mon Sep 17 00:00:00 2001 From: Elliot Graham Date: Fri, 9 Oct 2026 15:15:37 +0100 Subject: [PATCH] LR-MASK-OVERLAY: hide overlay when adjusting a mask (#519) --- crates/ui-egui/src/lib.rs | 5 ++++ crates/ui-egui/src/tests_masking.rs | 40 +++++++++++++++++++++++++++++ docs/parity.md | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 7684f00d8..ce48e946b 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -338,6 +338,11 @@ impl LightcraftApp { return r; } let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string()); + if r.is_ok() && id == "mask.adjust" { + // Judge local adjustments on the photo, without the selection overlay obscuring them. + // Keep it hidden after release; O / the overlay eye can show it again. + self.ui.mask_overlay = false; + } if r.is_ok() && id == "photo.label" { let label = params.get("label").and_then(Value::as_str).and_then(lightcraft_catalog::ColorLabel::parse); let text = match label { diff --git a/crates/ui-egui/src/tests_masking.rs b/crates/ui-egui/src/tests_masking.rs index 0729a854f..6166bb21b 100644 --- a/crates/ui-egui/src/tests_masking.rs +++ b/crates/ui-egui/src/tests_masking.rs @@ -39,6 +39,46 @@ fn develop(h: &Headless) -> lightcraft_develop::DevelopSettings { (*h.app.session.develop_of(id).unwrap_or_default()).clone() } +#[test] +fn mask_adjustments_hide_overlay_without_disabling_the_mask() { + use lightcraft_pipeline::Overlay; + let mut h = detail("panel.masking"); + exec(&mut h, "mask.add", json!({"kind": "linear", "start": [0.5, 0.2], "end": [0.5, 0.7]})); + assert!(h.app.ui.mask_overlay); + + // Changing the shape needs the overlay; changing the image needs an unobscured preview. + exec(&mut h, "mask.refine", json!({"value": 10.0})); + assert!(h.app.ui.mask_overlay); + let r = h.request("ui.clickWidget", json!({"id": "slider:exposure", "fx": 0.7}), T); + assert_eq!(r["ok"], true, "{r}"); + assert!(!h.app.ui.mask_overlay, "the exposure slider hides the overlay after release"); + let d = develop(&h); + assert!(d.masks[0].adjust.exposure > 0.0); + assert!(d.masks[0].visible, "hiding the overlay must not disable the mask"); + assert_eq!(crate::panels::detail::view_overlay(&h.app, &d), Overlay::None); + let mask = d.masks[0].clone(); + + let r = h.request("ui.key", json!({"key": "o"}), T); + assert_eq!(r["ok"], true, "{r}"); + assert!(h.app.ui.mask_overlay, "O can restore the overlay"); + assert_eq!(develop(&h).masks[0], mask, "toggling the overlay leaves the mask and its effect unchanged"); + assert!(matches!(crate::panels::detail::view_overlay(&h.app, &develop(&h)), Overlay::Mask { .. })); + + // Failed edits and global adjustments must not hide the mask overlay. + assert!(h.app.run("mask.adjust", json!({"id": 999, "values": {"exposure": 1.0}})).is_err()); + assert!(h.app.ui.mask_overlay); + exec(&mut h, "develop.set", json!({"control": "light.exposure", "value": 0.5})); + assert!(h.app.ui.mask_overlay); + + // Numeric edits/resets and Amount use the same command path as the sliders. + for values in [json!({"exposure": 0.0}), json!({"saturation": 20.0}), json!({"amount": 50.0})] { + exec(&mut h, "view.maskOverlay", json!({"show": true})); + exec(&mut h, "mask.adjust", json!({"values": values})); + assert!(!h.app.ui.mask_overlay); + } + h.settle(SETTLE); +} + #[test] fn mask_overlay_keys_and_pins() { use lightcraft_pipeline::{MaskView, Overlay}; diff --git a/docs/parity.md b/docs/parity.md index 4b992c99b..653aa784d 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -363,7 +363,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins. | LR-MASK-AMOUNT | Mask amount | P1 | ✅ | `cmd:mask.adjust` (`amount`), `crates/ui-egui/src/panels/masking.rs` | | | LR-MASK-FEATHER-EDGE | Refine mask edges | P2 | ✅ | `cmd:mask.refine`, `crates/pipeline/src/masks.rs` (`evaluate_one`) | Refine Edges slider per mask: guided filter on the photo's luminance snaps soft mask edges to the photo's edges (rendered on the CPU) | | LR-MASK-SLIDERS | Local adjustment sliders | P0 | ✅ | `cmd:mask.adjust`, `crates/pipeline/src/finish.rs` | every slider renders (CPU + GPU), incl. Noise, Moiré, Defringe (negative Defringe has no effect); no local curve or effect presets | -| LR-MASK-OVERLAY | Mask overlay | P0 | ✅ | `cmd:view.maskOverlay`, `cmd:view.maskOverlayMode`, `cmd:view.maskOverlayColor`, `crates/pipeline/src/visualize.rs` | rendered alpha of the selected mask (CPU + GPU): colour, colour on B&W, image on black/white, white on black; ⇧O cycles while masking; no auto-show on hover | +| LR-MASK-OVERLAY | Mask overlay | P0 | ✅ | `cmd:view.maskOverlay`, `cmd:view.maskOverlayMode`, `cmd:view.maskOverlayColor`, `crates/pipeline/src/visualize.rs` | rendered alpha of the selected mask (CPU + GPU): colour, colour on B&W, image on black/white, white on black; ⇧O cycles while masking; successful local adjustments (including Amount) hide the overlay until manually shown again with O / the eye; shape/edge edits retain it; no auto-show on hover | | LR-MASK-PINS | Pins | P1 | ✅ | `cmd:view.maskPins`, `crates/ui-egui/src/panels/detail.rs` | a pin per radial/linear/brush component: click selects its mask, drag moves it; no pins for range/AI components, no Auto mode | | LR-MASK-UPDATE | Recompute AI masks | P2 | ⬜ | | | | LR-MASK-SYNC | Copy masks to other photos | P1 | ✅ | `cmd:develop.copy` (`groups`) | |