LRC-DEV-VIEWOPTIONS: View Options can turn off the loupe's "Rendering…" message

View Options gets the Loupe View tab's General section with "Show message
when loading or rendering photos". It is on by default, so the loupe keeps
saying "Rendering…" as before. With it off, the loupe does not show the
"Rendering…" pill while an edit renders, or the "Rendering…" text over an
empty loupe; the "can't be opened" messages still show. The choice is saved
with the app settings, and `view.viewOptions {loupeMessages}` sets it for
an agent.
This commit is contained in:
dotCooCoo
2026-10-24 04:45:18 -07:00
parent 23f54803e6
commit a4ddb9198f
7 changed files with 106 additions and 7 deletions
+1
View File
@@ -2341,6 +2341,7 @@
"View Options": "Opzioni di visualizzazione",
"Loupe View": "Vista lente",
"Show Info Overlay": "Mostra sovrapposizione informazioni",
"Show message when loading or rendering photos": "Mostra un messaggio durante il caricamento o il rendering delle foto",
"Loupe Info 1": "Info lente 1",
"Loupe Info 2": "Info lente 2",
"Use Defaults": "Usa predefiniti",
+63
View File
@@ -4944,6 +4944,69 @@ mod tests {
assert!(!h.app.renderer.is_pending(Slot::Main));
}
/// View Options' Show message when loading or rendering photos (on by default): turned off in
/// the window, the loupe does not say "Rendering…" while an edit renders; `view.viewOptions
/// {loupeMessages}` turns it back on for an agent (refusing a value that is not true or false).
#[test]
fn view_options_turn_the_rendering_message_off() {
use crate::render::Slot;
use std::{cell::Cell, rc::Rc};
let pill = |h: &Headless| h.app.widgets.iter().any(|(w, _)| w == "loupe:rendering");
let mut h = demo([1000.0, 700.0]);
let t = Duration::from_secs(10);
h.request("ui.set", json!({"view": "detail", "right": "none"}), t);
h.settle(SETTLE);
assert!(h.app.ui.settings.loupe_messages, "on by default");
assert_eq!(h.request("ui.key", json!({"key": "j", "cmd": true}), t)["ok"], true);
h.step();
assert_eq!(h.request("ui.clickWidget", json!({"id": "check:viewOptions.loupeMessages"}), t)["ok"], true);
h.step();
assert!(!h.app.ui.settings.loupe_messages, "the tick box turns it off");
h.app.ui.dialog = None;
let open = Rc::new(Cell::new(false));
h.app.renderer.set_offload(Box::new(super::RenderGate::new(open.clone())));
h.request("engine.execute", json!({"command": "develop.set", "params": {"control": "light.exposure", "value": 0.5}}), t);
for _ in 0..45 {
h.step();
}
assert!(h.app.renderer.is_pending(Slot::Main), "the edit's render is held");
assert!(!pill(&h), "no message while it is off");
let r = h.request("engine.execute", json!({"command": "view.viewOptions", "params": {"loupeMessages": "yes"}}), t);
assert_eq!(r["ok"], false, "{r}");
assert!(!h.app.ui.settings.loupe_messages, "a refused change changes nothing");
let r = h.request("engine.execute", json!({"command": "view.viewOptions", "params": {"loupeMessages": true}}), t);
assert_eq!(r["result"]["loupeMessages"], true, "{r}");
assert_eq!(h.app.ui.dialog, None, "a value sets it without opening the window");
assert!(h.step_until(SETTLE, pill), "the message shows again while the render is held");
open.set(true);
assert!(h.step_until(SETTLE, |h| !pill(h)), "gone once the render lands");
}
/// With nothing to show yet, the loupe says "Rendering…" in its middle only while View Options'
/// Show message when loading or rendering photos is on.
#[test]
fn an_empty_loupe_says_rendering_only_with_the_message_on() {
use std::{cell::Cell, rc::Rc};
for on in [true, false] {
let mut h = demo([1000.0, 700.0]);
let t = Duration::from_secs(10);
h.app.ui.settings.loupe_messages = on;
h.app.renderer.set_offload(Box::new(super::RenderGate::new(Rc::new(Cell::new(false)))));
h.request("ui.set", json!({"view": "detail", "right": "none"}), t);
// drop every texture (quick previews and thumbnails land outside the gate) until a
// frame has nothing to show
let empty = (0..60).any(|_| {
h.app.renderer.textures.clear();
h.step();
h.app.loupe_shown.map(|l| l.1) == Some("none")
});
assert!(empty, "a frame with nothing rendered");
let said = h.painted_text().iter().any(|text| text == "Rendering…");
assert_eq!(said, on, "message on: {on}");
}
}
/// A render that fails while the loupe shows a stand-in (the embedded JPEG, a thumbnail) left
/// the stand-in up as if it were the photo: the loupe says the photo can't be opened, with the
/// error on hover, and keeps saying so while a library change retries it.
+14 -2
View File
@@ -742,9 +742,20 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
Ok(json!({"infoOverlay": app.ui.info_overlay}))
}
"view.viewOptions" => {
// `info1` / `info2` set the lines for an agent; without them the window opens
// `info1`, `info2` and `loupeMessages` set the options for an agent; without any of them the window opens
let mut info = app.ui.settings.loupe_info;
let mut set = false;
// `loupeMessages` sets Show message when loading or rendering photos
let messages = match p.get("loupeMessages") {
None => app.ui.settings.loupe_messages,
Some(v) => match v.as_bool() {
Some(on) => {
set = true;
on
}
None => return Some(Err("`loupeMessages`: true|false".into())),
},
};
for (key, lines) in [("info1", &mut info.info1), ("info2", &mut info.info2)] {
let Some(v) = p.get(key) else { continue };
match serde_json::from_value(v.clone()) {
@@ -757,10 +768,11 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
}
if set {
app.ui.settings.loupe_info = info;
app.ui.settings.loupe_messages = messages;
} else {
app.ui.dialog = Some(Dialog::ViewOptions);
}
Ok(json!({"loupeInfo": app.ui.settings.loupe_info}))
Ok(json!({"loupeInfo": app.ui.settings.loupe_info, "loupeMessages": app.ui.settings.loupe_messages}))
}
"view.chooseLayoutImage" => {
let path = match p.get("path").and_then(Value::as_str) {
+5 -2
View File
@@ -796,9 +796,10 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
);
p.text(canvas.center() + vec2(0.0, 12.0), Align2::CENTER_CENTER, e, t.font(12.0), t.text_dim);
}
None => {
None if app.ui.settings.loupe_messages => {
p.text(canvas.center(), Align2::CENTER_CENTER, crate::i18n::tr("Rendering…"), t.font(13.0), t.text_dim);
}
None => {}
}
}
}
@@ -950,9 +951,11 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
let over = if over.is_positive() { over } else { canvas };
match failed {
Some(e) => failed_pill(ui, app, &photo, canvas, over, nav, "loupe:renderFailed", e),
None => {
// View Options' Show message when loading or rendering photos
None if app.ui.settings.loupe_messages => {
loupe_pill(ui, canvas, over, nav, "loupe:rendering", crate::i18n::tr("Rendering…"), false);
}
None => {}
}
}
if let Some(why) = photo.preview_only.as_deref().filter(|_| !fullscreen) {
+4 -2
View File
@@ -691,8 +691,8 @@ fn interface_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
}
/// View ▸ View Options (⌘J), Lightroom Classic's Loupe View tab: whether the info overlay shows and
/// which, and the three lines each of Info 1 and Info 2 shows (`view.viewOptions`). Changes apply
/// as they are made.
/// which, the three lines each of Info 1 and Info 2 shows, and whether the loupe shows its loading
/// and rendering message (`view.viewOptions`). Changes apply as they are made.
pub(super) fn view_options(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
use crate::state::{InfoOverlay as I, LoupeInfo, LoupeInfoField as F};
heading(ui, t, crate::i18n::tr("Loupe View"));
@@ -728,6 +728,8 @@ pub(super) fn view_options(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Token
*lines = defaults;
}
}
heading(ui, t, crate::i18n::tr("General"));
check(ui, "viewOptions.loupeMessages", &mut app.ui.settings.loupe_messages, "Show message when loading or rendering photos");
}
// ----------------------------------------------------------------------------------- Slideshow
+18
View File
@@ -438,6 +438,9 @@ pub struct AppSettings {
pub film_badges: bool,
/// Grid: when to show the rating / flag / edited badges.
pub grid_badges: GridBadges,
/// View ▸ View Options ▸ Show message when loading or rendering photos: the loupe says
/// "Rendering…" while the photo it shows is not the finished render.
pub loupe_messages: bool,
/// Shortcuts the user changed (Help ▸ Keyboard Shortcuts): command id → shortcut, `""` = none.
pub keymap: crate::shortcuts::Keymap,
/// Auto (follow the system), Dark or Light.
@@ -483,6 +486,7 @@ impl Default for AppSettings {
film_names: true,
film_badges: true,
grid_badges: GridBadges::Auto,
loupe_messages: true,
keymap: Default::default(),
appearance_mode: AppearanceMode::Dark,
dark_theme: DarkTheme::Charcoal,
@@ -1631,4 +1635,18 @@ mod loupe_info_tests {
assert_eq!(newer.loupe_info, LoupeInfo::default());
assert!(!newer.film_names, "the rest of the settings are still read");
}
/// Show message when loading or rendering photos is on until it is turned off, and is saved
/// with the app's settings.
#[test]
fn the_loading_message_choice_is_saved_with_the_settings() {
assert!(AppSettings::default().loupe_messages);
let old: AppSettings = serde_json::from_value(serde_json::json!({"filmNames": false})).unwrap();
assert!(old.loupe_messages, "a settings file from before the choice existed keeps the message");
let settings = AppSettings { loupe_messages: false, ..AppSettings::default() };
let saved = serde_json::to_value(&settings).unwrap();
assert_eq!(saved["loupeMessages"], false);
let read: AppSettings = serde_json::from_value(saved).unwrap();
assert!(!read.loupe_messages);
}
}
+1 -1
View File
@@ -730,7 +730,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
| LRC-DEV-TAT | Targeted adjustment tools | P1 | ✅ | `cmd:develop.targeted` (`target`: curve / hue / sat / lum) | |
| LRC-DEV-DEFAULTS | Per-camera raw defaults | P1 | ✅ | `cmd:library.preferences` (`camera`, `import.perCamera`), `crates/ui-egui/src/panels/settings.rs` | Settings → Import: raw default and per-camera presets |
| LRC-DEV-PROCESS | Process versions, Update to Current Process | P1 | ✅ | `cmd:develop.updateProcess`, `cmd:develop.reset`, `cmd:develop.get`, `crates/develop/src/settings.rs`, `crates/pipeline/src/finish.rs`, `docs/process-versions.md` | each photo records the rendering process its settings are interpreted by: settings saved before this existed load as V1, new photos and Reset get the latest, virtual copies inherit, copy / paste / sync / presets keep each photo's own; Photo ▸ Update to Current Process (one undo step, enabled while a selected photo is on an older process); a number from a newer LightCraft is kept and renders with the newest known process. Only V1 exists so far: this is what lets a retuned default tone (#146) or stronger Light sliders (#196) ship without changing existing edits |
| LRC-DEV-VIEWOPTIONS | Develop view options | P2 | 🟡 | `cmd:view.viewOptions`, `crates/ui-egui/src/panels/settings.rs` (`view_options`) | View ▸ View Options… (⌘J), the Loupe View part: Show Info Overlay with Info 1 / Info 2, and the three lines of each (none, file name, capture date, dimensions, exposure, camera, lens, camera + lens) with Use Defaults; applied as they change, saved with the app settings; `view.viewOptions {info1, info2}` sets them for agents. Not yet: the Grid View tab, more fields (ratings, metadata, copy name), "show briefly when the photo changes", the loading-message and video-frame options |
| LRC-DEV-VIEWOPTIONS | Develop view options | P2 | 🟡 | `cmd:view.viewOptions`, `crates/ui-egui/src/panels/settings.rs` (`view_options`) | View ▸ View Options… (⌘J), the Loupe View part: Show Info Overlay with Info 1 / Info 2, and the three lines of each (none, file name, capture date, dimensions, exposure, camera, lens, camera + lens) with Use Defaults; General's Show message when loading or rendering photos (on by default; off, the loupe does not say "Rendering…"); applied as they change, saved with the app settings; `view.viewOptions {info1, info2, loupeMessages}` sets them for agents. Not yet: the Grid View tab, more fields (ratings, metadata, copy name), "show briefly when the photo changes", the video-frame option |
| LRC-DEV-LOUPEOVERLAY | Loupe overlay: layout image | P2 | 🟡 | `cmd:view.chooseLayoutImage`, `cmd:view.layoutImage`, `cmd:view.layoutImageOpacity`, `cmd:view.layoutImageMatte`, `crates/ui-egui/src/panels/layout_overlay.rs` | View ▸ Loupe Overlay: a PNG with transparency (a cover layout, a banner's safe areas) fitted and centred over the loupe photo, following zoom and pan; opacity and matte (the photo darkened outside the graphic's box) in steps; ⌥⌘O shows / hides it; read off the UI thread; saved with the UI state. Not yet: the Grid and Guides overlays, moving and resizing the layout with ⌘ held, free opacity / matte sliders |
| LRC-DEV-VIDEO | Video frame capture | P2 | ⬜ | | |
| LRC-MAP-VIEW | Map view | P2 | ⬜ | | |