LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections

Window > Edit Sections > Solo Mode (view.soloSections {on?}) and a right-click on
any section header turn it on: opening one section closes the others. Option-click
on a header opens just that section once. Kept with the UI state (singlePanel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matthew James Sivak
2026-10-13 14:13:48 +02:00
co-authored by Claude Opus 5.5
parent b3d392b499
commit d8f62a29b2
16 changed files with 152 additions and 8 deletions
+1
View File
@@ -529,6 +529,7 @@
"Filmstrip (/)": "Filmstreifen (/)",
"Filter": "Filter",
"Filter Bar": "Filterleiste",
"Solo Mode": "Solo-Modus",
"Filter Presets": "Filtervorgaben",
"Filter bar": "Filterleiste",
"Filter fields": "Filterfelder",
+1
View File
@@ -511,6 +511,7 @@
"Filmstrip (/)": "Tira de película (/)",
"Filter": "Filtro",
"Filter Bar": "Barra de filtros",
"Solo Mode": "Modo solo",
"Filter Presets": "Ajustes preestablecidos de filtro",
"Filter bar": "Barra de filtros",
"Filter fields": "Campos de filtro",
+1
View File
@@ -494,6 +494,7 @@
"Filmstrip (/)": "Pellicule (/)",
"Filter": "Filtre",
"Filter Bar": "Barre de filtres",
"Solo Mode": "Mode solo",
"Filter Presets": "Préréglages de filtre",
"Filter bar": "Barre de filtres",
"Filter fields": "Champs de filtre",
+1
View File
@@ -709,6 +709,7 @@
"Filmstrip": "フィルムストリップ",
"Filter": "フィルター",
"Filter Bar": "フィルターバー",
"Solo Mode": "ソロモード",
"Filter Presets": "フィルタープリセット",
"Filter bar": "フィルターバー",
"Filter fields": "項目を絞り込む",
+1
View File
@@ -502,6 +502,7 @@
"Filmstrip (/)": "Película (/)",
"Filter": "Filtro",
"Filter Bar": "Barra de filtros",
"Solo Mode": "Modo solo",
"Filter Presets": "Predefinições de filtro",
"Filter bar": "Barra de filtros",
"Filter fields": "Campos de filtro",
+1
View File
@@ -412,6 +412,7 @@
"Filmstrip (/)": "Лента (/)",
"Filter": "Фильтр",
"Filter Bar": "Панель фильтров",
"Solo Mode": "Режим соло",
"Filter Presets": "Пресеты фильтров",
"Filter bar": "Панель фильтров",
"Filter fields": "Поля фильтра",
+1
View File
@@ -915,6 +915,7 @@
"Fill Edges": "Заповнити краї",
"Filmstrip": "Стрічка",
"Filter Bar": "Панель фільтрів",
"Solo Mode": "Режим соло",
"Filter Presets": "Набори налаштувань фільтрів",
"Filter bar": "Панель фільтрів",
"Filter fields": "Поля фільтрів",
+1
View File
@@ -652,6 +652,7 @@
"Filmstrip (/)": "胶片窗格 (/)",
"Filter": "过滤器",
"Filter Bar": "过滤器栏",
"Solo Mode": "单独模式",
"Filter Presets": "过滤器预设",
"Filter bar": "过滤器栏",
"Filter fields": "过滤字段",
+1
View File
@@ -652,6 +652,7 @@
"Filmstrip (/)": "底片列(/)",
"Filter": "篩選",
"Filter Bar": "篩選列",
"Solo Mode": "單獨模式",
"Filter Presets": "篩選預設集",
"Filter bar": "篩選列",
"Filter fields": "篩選欄位",
+2
View File
@@ -110,6 +110,8 @@ mod tests_quit_unsaved;
#[cfg(test)]
mod tests_scroll;
#[cfg(test)]
mod tests_solo_sections;
#[cfg(test)]
mod tests_switch_library;
#[cfg(test)]
mod tests_sync;
+1
View File
@@ -328,6 +328,7 @@ pub fn checked(app: &LightcraftApp, id: &str) -> Option<bool> {
"view.beforeAfterSplit" => Some(u.before_after == crate::state::BeforeAfter::Split),
"view.autoAdvance" => Some(u.auto_advance),
"view.filterBar" => Some(u.filter_bar),
"view.soloSections" => Some(u.single_panel),
"view.navigator" => Some(u.navigator),
"view.fullScreenPreview" => Some(u.fullscreen),
"view.lightsOut" => Some(u.lights_out != crate::state::LightsOut::On),
+7
View File
@@ -129,6 +129,8 @@ pub const UI_COMMANDS: &[UiCommand] = &[
("section.effects", "Effects", Some("Cmd+3"), "Window>Edit Sections"),
("section.detail", "Detail", Some("Cmd+4"), "Window>Edit Sections"),
("section.optics", "Optics", Some("Cmd+5"), "Window>Edit Sections"),
// {on?}; no params toggles. Opening one Edit section closes the others.
("view.soloSections", "Solo Mode", None, "Window>Edit Sections"),
("tool.brush", "Brush", Some("B"), "Window>Tools"),
("tool.linear", "Linear Gradient", Some("L"), "Window>Tools"),
("tool.radial", "Radial Gradient", Some("R"), "Window>Tools"),
@@ -953,6 +955,11 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
}
Ok(json!({"hidden": app.ui.hidden_locations}))
}
"view.soloSections" => {
let on = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.single_panel);
app.ui.set_single_panel(on);
Ok(json!({"soloSections": app.ui.single_panel}))
}
"view.filterBar" => {
app.ui.filter_bar = !app.ui.filter_bar;
if app.ui.filter_bar && !matches!(app.ui.view, ViewMode::PhotoGrid | ViewMode::SquareGrid) {
+17 -1
View File
@@ -531,8 +531,24 @@ fn section(
if let Some(on) = toggled {
let _ = app.run("develop.sectionEnabled", json!({"section": id, "enabled": on}));
} else if resp.clicked() {
app.ui.toggle_section(id);
// ⌥-click opens just this section, as in Lightroom Classic
if ui.input(|i| i.modifiers.alt) {
app.ui.solo_section(id);
} else {
app.ui.toggle_section(id);
}
}
resp.context_menu(|ui| {
let mut solo = app.ui.single_panel;
let item = ui.checkbox(&mut solo, crate::i18n::tr("Solo Mode"));
register(ui.ctx(), "sectionMenu:solo", item.rect);
if item.clicked() {
if let Err(e) = app.run("view.soloSections", json!({"on": solo})) {
app.toast(ui.ctx(), e);
}
ui.close();
}
});
if open {
body(app, ui, d);
}
+12 -1
View File
@@ -523,7 +523,7 @@ pub struct UiState {
pub open_sections: Vec<String>,
/// Open flyouts (curve, mixer, grading…).
pub open_flyouts: Vec<String>,
/// Single-panel mode: opening one section closes the others.
/// Solo Mode (`view.soloSections`): opening one Edit section closes the others.
pub single_panel: bool,
pub show_clipping: bool,
pub histogram: bool,
@@ -1177,6 +1177,17 @@ impl UiState {
self.open_sections.push(id.to_string());
}
}
/// Opens `id` and closes every other Edit section (⌥-click on a section header).
pub fn solo_section(&mut self, id: &str) {
self.open_sections = vec![id.to_string()];
}
/// Turns Solo Mode on or off. Turning it on keeps only the section opened last.
pub fn set_single_panel(&mut self, on: bool) {
self.single_panel = on;
if on && let Some(last) = self.open_sections.pop() {
self.open_sections = vec![last];
}
}
/// Whether the sidebar section with this id is folded shut (an unknown id: no).
pub fn sidebar_section_collapsed(&self, id: &str) -> bool {
crate::sidebar::SidebarSection::from_id(id).is_some_and(|s| self.sidebar.is_collapsed(s))
+96
View File
@@ -0,0 +1,96 @@
//! Headless tests of Solo Mode: opening one Edit section closes the others (Lightroom Classic's
//! panel Solo Mode), from the Window menu command, the section header's menu and ⌥-click.
use std::time::Duration;
use serde_json::json;
use crate::headless::Headless;
use crate::{LightcraftApp, Services};
const T: Duration = Duration::from_secs(20);
/// Detail view with the Edit panel open and Light (the default) expanded.
fn edit_open() -> Headless {
let services = Services { png: None, ..Default::default() };
let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), services);
let mut h = Headless::new(app, [1200.0, 1400.0], 1.0);
let r = h.request("ui.set", json!({"view": "detail"}), T);
assert_eq!(r["ok"], true, "{r}");
h.app.ui.right = crate::state::RightPanel::Edit;
h.app.ui.open_sections = vec!["light".into()];
h.step();
h.step();
h
}
fn exec(h: &mut Headless, command: &str, params: serde_json::Value) -> serde_json::Value {
let r = h.request("engine.execute", json!({"command": command, "params": params}), T);
assert_eq!(r["ok"], true, "{command}: {r}");
r["result"].clone()
}
fn click(h: &mut Headless, id: &str, button: Option<&str>) {
let params = match button {
Some(b) => json!({"id": id, "button": b}),
None => json!({"id": id}),
};
let r = h.request("ui.clickWidget", params, T);
assert_eq!(r["ok"], true, "click {id}: {r}");
h.step();
}
#[test]
fn solo_mode_closes_the_other_sections_when_one_opens() {
let mut h = edit_open();
click(&mut h, "section:color", None);
assert_eq!(h.app.ui.open_sections, ["light", "color"], "off: sections open side by side");
assert_eq!(exec(&mut h, "view.soloSections", json!({})), json!({"soloSections": true}));
assert_eq!(h.app.ui.open_sections, ["color"], "turning it on keeps the section opened last");
assert_eq!(crate::menubar::checked(&h.app, "view.soloSections"), Some(true));
h.step();
click(&mut h, "section:detail", None);
assert_eq!(h.app.ui.open_sections, ["detail"]);
// the Window > Edit Sections shortcuts follow it too
exec(&mut h, "section.optics", json!({}));
assert_eq!(h.app.ui.open_sections, ["optics"]);
// closing the open one leaves none open
exec(&mut h, "section.optics", json!({}));
assert!(h.app.ui.open_sections.is_empty());
assert_eq!(exec(&mut h, "view.soloSections", json!({"on": false})), json!({"soloSections": false}));
exec(&mut h, "section.light", json!({}));
exec(&mut h, "section.color", json!({}));
assert_eq!(h.app.ui.open_sections, ["light", "color"]);
}
#[test]
fn the_section_header_menu_turns_solo_mode_on() {
let mut h = edit_open();
click(&mut h, "section:color", None);
click(&mut h, "section:effects", Some("right"));
click(&mut h, "sectionMenu:solo", None);
assert!(h.app.ui.single_panel, "the header's Solo Mode item turns it on");
assert_eq!(h.app.ui.open_sections, ["color"]);
}
#[test]
fn option_click_opens_just_that_section() {
let mut h = edit_open();
click(&mut h, "section:color", None);
let r = h.request("ui.clickWidget", json!({"id": "section:detail", "alt": true}), T);
assert_eq!(r["ok"], true, "{r}");
h.step();
assert_eq!(h.app.ui.open_sections, ["detail"], "⌥-click closes the others");
assert!(!h.app.ui.single_panel, "⌥-click doesn't turn Solo Mode on");
}
#[test]
fn solo_mode_survives_a_restart() {
let mut ui = crate::state::UiState::default();
ui.set_single_panel(true);
let back: crate::state::UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap();
assert!(back.single_panel);
}
+8 -6
View File
@@ -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 (LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections; 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
@@ -31,12 +31,12 @@ cited here still exists. Formerly docs/parity.md.
| C. Views & navigation (VIEW) | 15 | 1 | 1 | 0 | 8/9 (89%) | 4/4 (100%) |
| D. Search & filter (FILT) | 11 | 1 | 1 | 0 | 4/4 (100%) | 4/4 (100%) |
| E. Metadata (META) | 5 | 1 | 0 | 0 | 2/2 (100%) | 2/2 (100%) |
| F. Edit panel — global adjustments (EDIT) | 43 | 2 | 3 | 1 | 28/28 (100%) | 13/14 (93%) |
| F. Edit panel — global adjustments (EDIT) | 44 | 2 | 3 | 1 | 28/28 (100%) | 13/14 (93%) |
| G. Profiles (PROF) | 7 | 1 | 3 | 0 | 3/4 (75%) | 3/3 (100%) |
| H. Crop & rotate (CROP) | 9 | 0 | 0 | 1 | 6/6 (100%) | 3/3 (100%) |
| I. Remove / healing (REM) | 8 | 1 | 2 | 2 | 4/4 (100%) | 2/3 (67%) |
| J. Red eye (EYE) | 2 | 0 | 0 | 0 | — | 1/1 (100%) |
| K. Masking (MASK) | 13 | 5 | 5 | 0 | 7/8 (88%) | 5/5 (100%) |
| K. Masking (MASK) | 14 | 5 | 5 | 0 | 7/8 (88%) | 6/6 (100%) |
| L. Presets (PRE) | 6 | 0 | 1 | 1 | 2/2 (100%) | 2/2 (100%) |
| M. Versions & history (VER) | 5 | 0 | 0 | 0 | 1/1 (100%) | 3/3 (100%) |
| N. Copy / paste / sync (SYNC) | 5 | 0 | 0 | 0 | 3/3 (100%) | 1/1 (100%) |
@@ -53,9 +53,9 @@ cited here still exists. Formerly docs/parity.md.
| 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%) |
| Lightroom Classic extras | 34 | 16 | 35 | 8 | — | 24/26 (92%) |
| **Total** | 408 | 52 | 72 | 35 | 193/201 (96%) | 145/157 (92%) |
| **Total** | 410 | 52 | 72 | 35 | 193/201 (96%) | 146/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.6%** of 534 in-scope rows — P0 98.0% of 201 · P1 95.3% of 158 · P2 50.3% of 174.
<!-- /parity:summary -->
## Top gaps and shortcut notes
@@ -222,6 +222,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
| LR-EDIT-LENSBLUR | Lens blur | P2 | ⬜ | | settings field reserved, not rendered |
| LR-EDIT-CALIB | Calibration [Classic] | P1 | ✅ | `ctl:calibration.*` | shadows tint, red/green/blue primary hue and saturation; read/written in XMP |
| LR-EDIT-SECTION-TOGGLE | Section on/off | P1 | ✅ | `cmd:develop.sectionEnabled`, `crates/develop/src/lib.rs` (`effective`) | the eye on a section header hides its adjustments: Light (with the curve), Color (white balance, presence, mixers, grading, point colour), Effects, Detail, Optics, Geometry, Calibration; masks stay on (issue #316 fixed Light, Color and Detail, which had no effect) |
| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on. `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) |
| LR-EDIT-RESET | Reset all / section / slider | P0 | ✅ | `cmd:develop.reset`, `cmd:develop.resetSection`, `cmd:develop.resetControl`, `crates/ui-egui/src/widgets.rs` (double-click) | no "reset to open" |
| LR-EDIT-SHOWORIG | Show original | P0 | ✅ | `cmd:view.showOriginal` | |
@@ -764,7 +765,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
| LRC-WEB-IMAGEINFO | Web gallery image info | OOS | 🚫 | | |
| LRC-WEB-OUTPUT | Web gallery output | OOS | 🚫 | | |
| LRC-WEB-UPLOAD | Web gallery upload | OOS | 🚫 | | |
| KEYC-PANELS | Classic panel keys (Tab, ⇧Tab, T, F5–F8, solo) | P2 | ⬜ | | |
| KEYC-PANELS | Classic panel keys (Tab, ⇧Tab, T, F5–F8, solo) | P2 | ⬜ | | solo itself (⌥-click a header, Solo Mode) is done: see LR-EDIT-SECTION-SOLO |
| KEYC-MODULES | Classic module switching (⌘⌥1–7) | P2 | 🚫 | | no modules in LightCraft |
| KEYC-VIEWS | Classic view keys (E, G, C, N, L, F, I, ⇧R, ⌘⌥0) | P2 | 🟡 | `cmd:view.photoGrid`, `cmd:view.zoom100` | G works; E opens Edit (not loupe); no compare/survey/lights-out/screen modes |
| KEYC-SECONDWINDOW | Classic secondary-window keys | P2 | ⬜ | | |
@@ -789,6 +790,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections (Window ▸ Edit Sections, section-header menu, ⌥-click); KEYC-PANELS notes it |
| 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 (🟡) |