mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
commit
7627b2b2b8
4 files changed
+85
-4
No files matched your search
@@ -58,6 +58,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
("view.squareGrid", "Square Grid", None, "View"),
|
||||
// G: Photo Grid ↔ Square Grid (from other views: the photo grid)
|
||||
("view.gridToggle", "Grid", Some("G"), ""),
|
||||
("library.selectOnlyActive", "Select Only Active Photo", Some("Cmd+Shift+D"), "Edit"),
|
||||
("tool.guidedUpright", "Guided Upright", Some("Shift+G"), "Window>Tools"),
|
||||
("view.detail", "Detail", Some("D"), "View"),
|
||||
("view.compare", "Compare", Some("Shift+C"), "View"),
|
||||
@@ -477,6 +478,10 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
|
||||
}
|
||||
"view.appearance" => appearance_command(app, p),
|
||||
"view.theme" => theme_command(app, p),
|
||||
"library.selectOnlyActive" => {
|
||||
let Some(active) = app.session.active() else { return Some(Err("no active photo".into())) };
|
||||
app.run("library.select", json!({"ids": [active.0], "active": active.0}))
|
||||
}
|
||||
"view.gridToggle" => {
|
||||
app.ui.view = if app.ui.view == ViewMode::PhotoGrid { ViewMode::SquareGrid } else { ViewMode::PhotoGrid };
|
||||
Ok(Value::Null)
|
||||
@@ -1847,6 +1852,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
|
||||
|
||||
pub fn ui_enabled(app: &LightcraftApp, id: &str) -> bool {
|
||||
match id {
|
||||
"library.selectOnlyActive" => app.session.active().is_some(),
|
||||
"dialog.contactSheet" | "app.contactSheet" => !cfg!(target_arch = "wasm32") && app.session.active().is_some() && app.export.is_none(),
|
||||
s if s.starts_with("panel.") || s.starts_with("tool.") || s.starts_with("section.") => app.session.active().is_some() || s == "panel.close",
|
||||
"app.export" | "dialog.export" | "dialog.createPreset" | "dialog.rename" | "dialog.captureTime" | "dialog.copySettings" => {
|
||||
|
||||
@@ -611,6 +611,74 @@ mod tests {
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
}
|
||||
|
||||
/// Press the default Select Only Active Photo chord (the command key on every platform).
|
||||
fn select_only_active_chord(h: &mut crate::headless::Headless) {
|
||||
let r = h.request("ui.key", json!({"key": "D", "cmd": true, "shift": true}), std::time::Duration::from_secs(20));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_only_active_keeps_the_active_photo_and_the_edit_undo_step() {
|
||||
use crate::state::ViewMode;
|
||||
use lightcraft_catalog::PhotoId;
|
||||
for view in [ViewMode::PhotoGrid, ViewMode::SquareGrid, ViewMode::Detail] {
|
||||
let mut h = library();
|
||||
h.app.ui.view = view;
|
||||
h.app.session.execute("library.select", &json!({"ids": [1, 2, 3], "active": 2})).unwrap();
|
||||
h.app.session.execute("photo.rate", &json!({"rating": 4})).unwrap();
|
||||
select_only_active_chord(&mut h);
|
||||
assert_eq!(h.app.session.selection.ids, vec![PhotoId(2)]);
|
||||
assert_eq!(h.app.session.active(), Some(PhotoId(2)));
|
||||
// Narrowing the selection does not replace the last photo-edit undo step.
|
||||
h.app.session.execute("edit.undo", &json!({})).unwrap();
|
||||
for id in 1..=3 {
|
||||
assert_eq!(h.app.session.catalog.photo(PhotoId(id)).unwrap().rating, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_only_active_yields_to_a_focused_text_field() {
|
||||
use lightcraft_catalog::PhotoId;
|
||||
let mut h = library();
|
||||
h.app.session.execute("library.select", &json!({"ids": [1, 2], "active": 2})).unwrap();
|
||||
let r = h.request("ui.clickWidget", json!({"id": "field:search"}), std::time::Duration::from_secs(20));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
select_only_active_chord(&mut h);
|
||||
assert_eq!(h.app.session.selection.ids, vec![PhotoId(1), PhotoId(2)]);
|
||||
assert_eq!(h.app.session.active(), Some(PhotoId(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_only_active_follows_the_keymap() {
|
||||
use lightcraft_catalog::PhotoId;
|
||||
let mut h = library();
|
||||
let both = vec![PhotoId(1), PhotoId(2)];
|
||||
h.app.session.execute("library.select", &json!({"ids": [1, 2], "active": 2})).unwrap();
|
||||
assert_eq!(super::shortcut_of(&h.app.ui.settings.keymap, "library.selectOnlyActive"), Some("Cmd+Shift+D"));
|
||||
// unbound: the default chord does nothing
|
||||
super::assign(&mut h.app.ui.settings.keymap, "library.selectOnlyActive", None).unwrap();
|
||||
select_only_active_chord(&mut h);
|
||||
assert_eq!(h.app.session.selection.ids, both);
|
||||
// rebound: the new key narrows the selection and the old chord still does not
|
||||
super::assign(&mut h.app.ui.settings.keymap, "library.selectOnlyActive", Some("F9")).unwrap();
|
||||
select_only_active_chord(&mut h);
|
||||
assert_eq!(h.app.session.selection.ids, both);
|
||||
key(&mut h, "F9", false);
|
||||
assert_eq!(h.app.session.selection.ids, vec![PhotoId(2)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_only_active_without_active_photo_does_not_change_selection() {
|
||||
let mut h = library();
|
||||
// Deselect All keeps the active photo selected, so clear the whole selection state instead.
|
||||
h.app.session.selection = Default::default();
|
||||
assert!(!crate::menus::ui_enabled(&h.app, "library.selectOnlyActive"));
|
||||
assert!(h.app.run("library.selectOnlyActive", json!({})).is_err());
|
||||
select_only_active_chord(&mut h);
|
||||
assert!(h.app.session.selection.ids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn library_rating_labels_flags_and_undo() {
|
||||
use lightcraft_catalog::{ColorLabel, Flag, PhotoId};
|
||||
|
||||
@@ -4,6 +4,7 @@ Select photos in Photo Grid or Square Grid, then use:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Cmd+Shift+D` (macOS) / `Ctrl+Shift+D` (Windows/Linux) | Select only the active photo |
|
||||
| `0` | Clear the star rating |
|
||||
| `1`–`5` | Set the star rating |
|
||||
| `[` / `]` | Decrease / increase each selected photo's rating by one, within 0–5 |
|
||||
@@ -13,6 +14,10 @@ Select photos in Photo Grid or Square Grid, then use:
|
||||
| `U` | Clear the flag |
|
||||
| `Shift` + `0`–`9`, `P`, `X` or `U` | Apply the action and select the next photo |
|
||||
|
||||
Select Only Active Photo (Edit menu) narrows the selection to the active photo. It does not change
|
||||
the photo, its edits or the last edit Undo step, and it works in any view that has an active photo.
|
||||
It can be reassigned or disabled in Help → Keyboard Shortcuts; text fields and dialogs keep the key.
|
||||
|
||||
Purple and clearing a colour label are available in Photo → Set Color Label and the photo context
|
||||
menu. Purple has no default number key, matching Lightroom Classic. Rejection marks a photo for
|
||||
culling; it does not delete the original or remove it from the library.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Lightroom parity checklist
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (LR-MERGE-HDR: merges of raws without colour matrices keep their colour (#965); rapid slider entry; LR-PROF-CAMERACOLOR: tone curve from black-and-white camera JPEGs; LR-VIEW-ZOOM, LR-VIEW-COMPARE: Compare and Reference zoom windows; LR-IMP-FORMATS, LR-IMP-CAMERA-COVERAGE: EOS R5 Mark II CR3 decodes; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md; filesystem history retry/Undo/Redo evidence; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (KEYC-SELECTONLYACTIVE: Select Only Active Photo, ⌘⇧D; LR-MERGE-HDR: merges of raws without colour matrices keep their colour (#965); rapid slider entry; LR-PROF-CAMERACOLOR: tone curve from black-and-white camera JPEGs; LR-VIEW-ZOOM, LR-VIEW-COMPARE: Compare and Reference zoom windows; LR-IMP-FORMATS, LR-IMP-CAMERA-COVERAGE: EOS R5 Mark II CR3 decodes; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md; filesystem history retry/Undo/Redo evidence; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md) · **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
|
||||
@@ -51,11 +51,11 @@ cited here still exists. Formerly docs/parity.md.
|
||||
| W. Cloud & AI infrastructure (CLOUD / AI) | 1 | 0 | 1 | 2 | — | — |
|
||||
| X. Cross-cutting behaviours (BEHAV) | 23 | 6 | 1 | 1 | 8/8 (100%) | 7/10 (70%) |
|
||||
| Y. Menus | 84 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 76 | 3 | 3 | 1 | 49/52 (94%) | 23/24 (96%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 76 | 4 | 3 | 1 | 49/52 (94%) | 23/25 (92%) |
|
||||
| Lightroom Classic extras | 34 | 16 | 35 | 8 | — | 24/26 (92%) |
|
||||
| **Total** | 408 | 52 | 72 | 35 | 193/201 (96%) | 145/157 (92%) |
|
||||
| **Total** | 408 | 53 | 72 | 35 | 193/201 (96%) | 145/158 (92%) |
|
||||
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **81.6%** of 532 in-scope rows — P0 98.0% of 201 · P1 95.2% of 157 · P2 50.0% of 173.
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **81.5%** of 533 in-scope rows — P0 98.0% of 201 · P1 94.9% of 158 · P2 50.0% of 173.
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps and shortcut notes
|
||||
@@ -610,6 +610,7 @@ From `06-shortcuts.md` part 1. Evidence is our binding; conflicts are explained
|
||||
| KEY-ZOOM | Zoom in / out — ⌘= / ⌘− | P0 | ✅ | `cmd:view.zoomIn`, `cmd:view.zoomOut`, `crates/ui-egui/src/tests_zoom_keys.rs` | the keys zoom the photo only: egui's own interface zoom on ⌘= / ⌘− / ⌘0 (Ctrl on Windows and Linux, where no native menu bar took the keys first) is off (issue #566) |
|
||||
| KEY-SELECTALL | Select all — ⌘A | P0 | ✅ | `cmd:library.selectAll` | |
|
||||
| KEY-SELECTNONE | Select none — ⌘D | P0 | ✅ | `cmd:library.selectNone` | secondary binding (primary ⌘⇧A) |
|
||||
| KEYC-SELECTONLYACTIVE | Select only active photo — ⌘⇧D / Ctrl+Shift+D | P1 | 🟡 | `cmd:library.selectOnlyActive`, `crates/ui-egui/src/shortcuts.rs` (`select_only_active_*` tests) | Edit → Select Only Active Photo; keeps the active photo, narrows the selection to it and leaves the edit Undo step alone; works in any view that has an active photo; rebindable in Help → Keyboard Shortcuts; text fields and dialogs keep their keys; covered by headless key tests, the macOS native-menu path is not verified |
|
||||
| KEY-PASTESELECTED | Paste selected — ⇧⌘V | P0 | ✅ | `cmd:dialog.pasteSettings` | |
|
||||
| KEY-PREFS | Settings — ⌘, | P0 | ✅ | `cmd:app.settings` | |
|
||||
| KEY-SEARCH | Search — ⌘F | P0 | ✅ | `cmd:view.focusSearch` | |
|
||||
@@ -790,6 +791,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-13 | patch | LR-VIEW-COMPARE, LRC-LIB-COMPARE: ⌘/Ctrl- and Shift-clicks on the filmstrip in Compare change the selection as in the grid; the two photos shown stay selected and the active photo is unchanged (#187); statuses unchanged |
|
||||
| 2026-10-13 | minor | KEYC-SELECTONLYACTIVE: Edit → Select Only Active Photo (⌘⇧D) lands as a new row; status 🟡 (headless-tested, native macOS menu path unverified) |
|
||||
| 2026-10-11 | minor | LR-MERGE-HDR: HDR and panorama merges of raws without colour matrices (CR2, NEF, ARW…) write the colour they open with as DNG tags (issue #965); status unchanged |
|
||||
| 2026-10-12 | minor | LR-META-XMP: Read Metadata from File no longer auto-writes the sidecar it has just read, which added `lc:settings` and hid another app's later `crs:` edits (#934); status unchanged (✅) |
|
||||
| 2026-10-12 | patch | LR-IMP-CAMERA-COVERAGE: CR2 white level from the active area, not the saturated masked rows above it (issue #1272); status unchanged (🟡) |
|
||||
|
||||
Reference in new issue
Block a user