Merge PR #2450: LRC-LIB-COMPARE: the Info panel labels the active photo while culling…

crates/ui-egui/src/tests_compare.rs: both sides' tests

docs/parity-checklist.md: header and revision rows from both sides
This commit is contained in:
aiongg
2026-10-22 02:40:11 +00:00
5 changed files with 57 additions and 7 deletions
+9 -1
View File
@@ -321,7 +321,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));
@@ -353,6 +353,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)
}
+3 -1
View File
@@ -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();
}
}
+7 -2
View File
@@ -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 {
+34
View File
@@ -284,3 +284,37 @@ fn deleting_the_pinned_reference_stops_drawing_it_and_reentry_uses_a_live_photo(
h.settle(SETTLE);
assert!(h.app.renderer.textures.get(&crate::render::Slot::Compare(0)).is_some_and(|t| t.photo == reference));
}
/// 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<u64> = 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");
}
+4 -3
View File
@@ -1,6 +1,6 @@
# Lightroom parity checklist
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; earlier: LRC-PRINT-PAGE: Print Page Options) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; earlier: LRC-PRINT-PAGE: Print Page Options) · **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:<pane>`) or “This photo can't be opened” (`compare:renderFailed:<pane>`) 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`, `cmd:view.viewOptions` | off / Info 1 (by default file name, capture date, size) / Info 2 (file name, exposure, camera + lens), each three lines chosen in View Options; ⌘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:<pane>`) or “This photo can't be opened” (`compare:renderFailed:<pane>`) as the loupe does. Escape → Detail without losing edits; pins are catalog-local; missing/deleted photos refused and deleted references no longer drawn (Windows regression/native smoke verified) |
| 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-20 | patch | LR-VIEW-ZOOM: the 20 ms rule clears a slow-drag verdict only on a drag timed at full size; a drag that drafts small leaves it unchanged, as the code does; status unchanged |
| 2026-10-19 | patch | LR-VIEW-ZOOM: the slow slider-drag verdict is described as per photo and control, as the code keys it; status unchanged |
| 2026-10-15 | minor | LR-VIEW-ZOOM: a slow slider-drag verdict needs two drags over 20 ms and expires after 30 s (doubling to 240 s); hidden-window frame times are ignored; the slow set forgets its oldest entry, not all (issue #330); status unchanged |