From f16c6ef07f32eb3da61f318a65264e529243baf7 Mon Sep 17 00:00:00 2001 From: Bruno-Alumn <171815746+Bruno-Alumn@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:49:03 +0000 Subject: [PATCH] LRC-LIB-COMPARE: the Info panel labels the active photo while culling (#2398) In Compare and Survey the selection holds both photos on screen, so the Info panel's colour-label swatches and stars (and the Set Color Label context menu) applied to the whole selection, while the bottom bar and the rating/label keys act on the active photo only. They now go through compare::target_active like the bottom bar does. The culling panes' captions showed stars and flags but not the colour label, so labelling there looked like it did nothing; each caption now shows the label as a dot. --- crates/ui-egui/src/panels/compare.rs | 10 +++++++- crates/ui-egui/src/panels/grid.rs | 4 +++- crates/ui-egui/src/panels/right.rs | 9 ++++++-- crates/ui-egui/src/tests_compare.rs | 34 ++++++++++++++++++++++++++++ docs/parity-checklist.md | 7 +++--- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/panels/compare.rs b/crates/ui-egui/src/panels/compare.rs index 6c1b5ba18..84b8c0c9f 100644 --- a/crates/ui-egui/src/panels/compare.rs +++ b/crates/ui-egui/src/panels/compare.rs @@ -317,7 +317,7 @@ fn photo_tile(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, slot: Slo super::detail::loupe_pill(ui, img_area, over, None, &format!("compare:rendering:{i}"), crate::i18n::tr("Rendering…"), false); } } - // caption: label · file name · stars · flag + // caption: label · file name · stars · flag · colour label // caption right under the image (at the bottom when zoomed in) let cap_top = (display.bottom() + 2.0).min(img_area.bottom()); let cap = Rect::from_min_max(pos2(img.left().max(area.left()), cap_top), pos2(area.right(), cap_top + caption_h)); @@ -349,6 +349,14 @@ fn photo_tile(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, slot: Slo Flag::Reject if fr.right() < cap.right() => paint(pt, fr, Icon::FlagReject, t.reject), _ => {} } + // the colour label as a dot, so labelling while culling shows on the photo (#2398) + let lr = Rect::from_min_size(pos2(fr.right() + 6.0, cap.center().y - 5.0), vec2(10.0, 10.0)); + if let Some(l) = photo.label + && lr.right() < cap.right() + { + pt.circle_filled(lr.center(), 5.0, super::grid::label_color(l)); + register(ui.ctx(), format!("cull:{}:label", id.0), lr); + } register(ui.ctx(), format!("cull:{}", id.0), display); (display, resp) } diff --git a/crates/ui-egui/src/panels/grid.rs b/crates/ui-egui/src/panels/grid.rs index c6365b784..75eb5c7b5 100644 --- a/crates/ui-egui/src/panels/grid.rs +++ b/crates/ui-egui/src/panels/grid.rs @@ -970,7 +970,9 @@ pub fn toast_skipped(app: &mut LightcraftApp, ctx: &egui::Context, r: &serde_jso pub fn label_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) { let current = app.session.active().and_then(|id| app.session.catalog.photo(id)).and_then(|p| p.label); if let Some(l) = label_items(app, ui, current, "photoLabel") { - let _ = app.run("photo.label", json!({"label": label_param(l)})); + let mut params = json!({"label": label_param(l)}); + crate::panels::compare::target_active(app, &mut params); + let _ = app.run("photo.label", params); ui.close(); } } diff --git a/crates/ui-egui/src/panels/right.rs b/crates/ui-egui/src/panels/right.rs index 537330810..290a9e9c8 100644 --- a/crates/ui-egui/src/panels/right.rs +++ b/crates/ui-egui/src/panels/right.rs @@ -643,7 +643,9 @@ fn info(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { } ui.add_space(8.0); if let Some(r) = ui.horizontal(|ui| crate::widgets::stars(ui, "info", p.rating, 20.0)).inner { - let _ = app.run("photo.rate", json!({"rating": r})); + let mut params = json!({"rating": r}); + crate::panels::compare::target_active(app, &mut params); + let _ = app.run("photo.rate", params); } ui.add_space(6.0); // colour label: one swatch per label (hover shows its name); clicking the current one clears it @@ -660,7 +662,10 @@ fn info(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { } let resp = resp.on_hover_text(crate::i18n::color_label(&app.session.catalog, l)); if resp.clicked() { - let _ = app.run("photo.label", json!({"label": if on { "none".to_string() } else { key }})); + // in Compare / Survey only the active photo, as the rating keys and the bottom pill do + let mut params = json!({"label": if on { "none".to_string() } else { key }}); + crate::panels::compare::target_active(app, &mut params); + let _ = app.run("photo.label", params); } } if let Some(l) = p.label { diff --git a/crates/ui-egui/src/tests_compare.rs b/crates/ui-egui/src/tests_compare.rs index 0f8d123be..7b87d2e47 100644 --- a/crates/ui-egui/src/tests_compare.rs +++ b/crates/ui-egui/src/tests_compare.rs @@ -209,3 +209,37 @@ fn a_compare_pane_says_when_its_render_failed() { assert!(h.step_until(SETTLE, failed), "the failure is said"); assert!(!h.app.widgets.iter().any(|(w, _)| w.starts_with("compare:rendering:"))); } + +/// Clicking a colour label (or stars) in the Info panel while culling labels the active photo +/// only, as the label keys and the bottom bar's stars and flags do, and the label shows under that +/// photo in Compare (#2398: "Can't assign color to photo when culling"). +#[test] +fn info_panel_label_in_compare_labels_the_active_photo_and_shows_it() { + use lightcraft_catalog::{ColorLabel, PhotoId}; + let mut h = demo(); + let vis: Vec = h.app.session.visible_cloned().iter().map(|p| p.0).collect(); + for id in &vis[..2] { + h.request("engine.execute", json!({"command": "photo.label", "params": {"label": "none", "ids": [id]}}), T); + h.request("engine.execute", json!({"command": "photo.rate", "params": {"rating": 0, "ids": [id]}}), T); + } + h.request("engine.execute", json!({"command": "library.select", "params": {"ids": [vis[0], vis[1]], "active": vis[0]}}), T); + let r = h.request("engine.execute", json!({"command": "view.compare"}), T); + assert_eq!(r["ok"], true, "{r}"); + let (sel, cand) = (r["result"]["select"].as_u64().unwrap(), r["result"]["candidate"].as_u64().unwrap()); + h.app.ui.right = crate::state::RightPanel::Info; + h.settle(SETTLE); + let photo = |h: &Headless, id: u64| h.app.session.catalog.photo(PhotoId(id)).cloned().expect("photo"); + let widgets = |h: &mut Headless| h.request("ui.widgets", json!({}), T)["result"].to_string(); + assert!(!widgets(&mut h).contains(&format!("cull:{cand}:label")), "no label yet"); + let r = h.request("ui.clickWidget", json!({"id": "label:red"}), T); + assert_eq!(r["ok"], true, "{r}"); + h.settle(SETTLE); + assert_eq!(photo(&h, cand).label, Some(ColorLabel::Red), "the active photo (the candidate) is labelled"); + assert_eq!(photo(&h, sel).label, None, "the select is not labelled with it"); + assert!(widgets(&mut h).contains(&format!("cull:{cand}:label")), "the label shows under the candidate"); + let r = h.request("ui.clickWidget", json!({"id": "star:info:3"}), T); + assert_eq!(r["ok"], true, "{r}"); + h.settle(SETTLE); + assert_eq!(photo(&h, cand).rating, 3); + assert_eq!(photo(&h, sel).rating, 0, "the select is not rated with it"); +} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 4e0531793..60f56109e 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (LRC-PRINT-PAGE: Print Page Options) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** patch (LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -136,7 +136,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-VIEW-NAVIGATOR | Navigator mini map | P1 | ✅ | `cmd:view.navigator`, `crates/ui-egui/src/panels/detail.rs` | shown while zoomed (bottom right); click/drag pans | | LR-VIEW-BEFOREAFTER | Before / after | P0 | ✅ | `cmd:view.showOriginal`, `cmd:view.beforeAfter`, `cmd:view.beforeAfterSplit`, `cmd:view.beforeAfterTopBottom`, `cmd:view.beforeAfterSplitTopBottom`, `cmd:beforeAfter.setBefore`, `cmd:beforeAfter.copyAfterToBefore`, `cmd:beforeAfter.copyBeforeToAfter`, `cmd:beforeAfter.swap`, `cmd:beforeAfter.resetBefore` | all four layouts; before = the import state (defaults + import preset) or a chosen history step / version / the current settings; copy and swap (View → Before/After Settings, History row menu). The chosen before lasts for the session; in side by side and top / bottom, clicks with the white-balance selector, colour samplers and targeted adjustment work in the Before view too (`crates/ui-egui/src/tests_before_after.rs`). Zoomed in, the side by side and stacked views each stay in their pane, with their labels under it. Click zoom animates both panes together, including cached window tiles; input follows the displayed image and render requests use the final size (#1821) | | LR-VIEW-COMPARE | Compare two photos | P1 | ✅ | `cmd:view.compare`, `cmd:compare.swap`, `cmd:compare.makeSelect`, `cmd:compare.set`, `crates/ui-egui/src/panels/compare.rs`, `crates/ui-egui/src/tests_compare.rs` | select / candidate, synced zoom + pan, arrows move the candidate; a filmstrip click puts the photo into the active side (a photo on the other side swaps), so the active photo is always one of the two shown; a ⌘/Ctrl- or Shift-click on the filmstrip changes the selection as in the grid while the two photos shown stay selected and the active photo is unchanged (#187); 1:1 to 8:1 sharp on both sides (zoom windows, see LR-VIEW-ZOOM); no zoom-link toggle; each pane says “Rendering…” (widget `compare:rendering:`) or “This photo can't be opened” (`compare:renderFailed:`) as the loupe does | -| LR-VIEW-SURVEY | Survey view [Classic] | P2 | ✅ | `cmd:view.survey`, `crates/ui-egui/src/panels/compare.rs` | selection tiled (≤ 48), keys act on the active photo, hover × removes | +| LR-VIEW-SURVEY | Survey view [Classic] | P2 | ✅ | `cmd:view.survey`, `crates/ui-egui/src/panels/compare.rs` | selection tiled (≤ 48), keys, the bottom bar and the Info panel's stars and colour labels act on the active photo, each tile's caption shows its colour label, hover × removes | | LR-VIEW-INFOOVERLAY | Info overlay on the photo | P1 | ✅ | `cmd:view.infoOverlay` | off / file + date + size / exposure + camera; ⌘I cycles (I in full screen; elsewhere I stays the Info panel) | | LR-VIEW-SLIDESHOW | Slideshow | P2 | ✅ | `cmd:view.slideshow` | View ▸ Slideshow (⌥⌘↩): the photos in view full screen from the selected one, played with the library's playback settings (see LRC-SS-PLAYBACK: fades, random order, repeat, previews prepared ahead; `interval` / any setting overrides one run); Space pauses, ←/→ step (a cut), Esc ends; playback controls on pointer movement | | LR-VIEW-SECONDWINDOW | Second display window [Classic] | P2 | ✅ | `cmd:view.secondWindow`, `crates/ui-egui/src/panels/second.rs` | Window ▸ Second Window (⌘F11): the active photo fitted in its own native window with its own render (a floating panel where there are no native windows); loupe view only (no grid / compare / survey there) | @@ -694,7 +694,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06- | LRC-LIB-AUTOIMPORT | Watched-folder import | P2 | ✅ | `cmd:library.autoImport` | see LR-IMP-AUTO | | LRC-LIB-TETHER | Tethered capture | P2 | ⬜ | | | | LRC-LIB-VIEWS | Grid / loupe / compare / survey / people | P1 | 🟡 | `cmd:view.photoGrid`, `cmd:view.squareGrid`, `cmd:view.detail`, `cmd:view.compare`, `cmd:view.survey`, `cmd:view.gridInfo` | grid, square grid, loupe, compare, survey, second window; square-grid captions: file name, exposure or capture date (View ▸ Grid Info; no people view; picking one in Photo Grid switches to Square Grid) | -| LRC-LIB-COMPARE | Compare view | P1 | ✅ | `cmd:view.compare` | ⇧C (C is Crop here); swap, make select; a filmstrip click replaces the active side, a ⌘/Ctrl- or Shift-click changes the selection without touching the pair; zoom always linked; each pane says “Rendering…” or that the photo can't be opened, as the loupe does (see LR-VIEW-COMPARE) | +| LRC-LIB-COMPARE | Compare view | P1 | ✅ | `cmd:view.compare` | ⇧C (C is Crop here); swap, make select; a filmstrip click replaces the active side, a ⌘/Ctrl- or Shift-click changes the selection without touching the pair; zoom always linked; each pane says “Rendering…” or that the photo can't be opened, as the loupe does (see LR-VIEW-COMPARE); the Info panel's stars and colour labels (and the Set Color Label menu) act on the active photo only, and each pane's caption shows its colour label (#2398) | | LRC-LIB-SURVEY | Survey view | P2 | ✅ | `cmd:view.survey` | N | | LRC-LIB-REFVIEW | Reference view | P2 | ✅ | `cmd:view.reference`, `cmd:photo.setReference`, `crates/ui-egui/src/panels/compare.rs` (`show_reference`) | View ▸ Reference View (⇧R): the reference photo beside the active one (edited with the Edit panel); photo menu ▸ Set as Reference Photo; each pane says “Rendering…” (widget `compare:rendering:`) or “This photo can't be opened” (`compare:renderFailed:`) as the loupe does | | LRC-LIB-CATALOG-PANEL | Catalog sets | P1 | ✅ | `cmd:library.source` (`missing`) | all, recently added, picks, recently deleted, missing photos (when any) | @@ -797,6 +797,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-20 | patch | LRC-LIB-COMPARE, LR-VIEW-SURVEY: in Compare and Survey the Info panel's stars and colour labels and the Set Color Label menu act on the active photo only (they labelled the whole selection), and the panes show the colour label (#2398); statuses unchanged | | 2026-10-14 | patch | LR-EXP-TYPE: opaque 8-bit PNG/TIFF/WebP use RGB; transparent exports retain alpha (#1670). Statuses and feature counts unchanged. | | 2026-10-15 | minor | LRC-PRINT-PAGE: Page Options (Page Numbers, Page Info, Crop Marks) on Single Image and Contact Sheet pages, in the Print dialog, `print.set` and templates; still 🟡 (no identity plate, watermark, photo info or Cut Guides) | | 2026-10-15 | minor | LR-IMP-FORMATS: Hasselblad 3FR and FFF show their uncompressed RGB preview (no JPEG in these files) in the grid and loupe until developed, and as the stand-in when a file fails to decode; status unchanged (🟡) |