Move the library's routes and status onto its own Slint global

The scan/opening/status/error lines, offline mode and its retry, pinning a
collection offline, the sync and thumbnail-sweep state, and the callbacks
that route the grid to a rescan, a sync, another library, a panorama merge
or an export — the last of what AppWindow still carried under the
library- prefix — move onto the `Library` global started earlier on this
branch. Rust reaches them through window.global::<Library>() rather than
window.set_/get_/on_/invoke_ on the root.

library-visible is the one name that stays: it is computed from
active-page and active-view, the shell's own routing state, which a
global cannot read. AppWindow now declares no other library- property or
callback.
This commit is contained in:
2026-09-20 22:22:55 +02:00
parent 6bf67cefc4
commit 1e472fd251
20 changed files with 542 additions and 433 deletions
+34 -14
View File
@@ -168,7 +168,9 @@ fn apply_keyword(window: &AppWindow, ctl: &Rc<LibraryController>, word: &str, as
Err(e) => {
// `BadName` carries text written to be read by the user rather than
// by a developer, so it is shown as it is.
window.set_library_error(format!("{e}").into());
window
.global::<Library>()
.set_library_error(format!("{e}").into());
return;
}
};
@@ -177,7 +179,9 @@ fn apply_keyword(window: &AppWindow, ctl: &Rc<LibraryController>, word: &str, as
// in the vocabulary, ready for the photographs it was typed for — so only
// the removal half needs a selection to act on.
if images.is_empty() && !assigning {
window.set_library_status("Select an image first".into());
window
.global::<Library>()
.set_library_status("Select an image first".into());
return;
}
@@ -197,13 +201,19 @@ fn apply_keyword(window: &AppWindow, ctl: &Rc<LibraryController>, word: &str, as
let n = match outcome {
Ok(n) => n,
Err(e) => {
window.set_library_error(format!("{e}").into());
window
.global::<Library>()
.set_library_error(format!("{e}").into());
return;
}
};
window.set_library_error(slint::SharedString::new());
window.set_library_status(keyword_summary(&word, n, images.len(), assigning).into());
window
.global::<Library>()
.set_library_error(slint::SharedString::new());
window
.global::<Library>()
.set_library_status(keyword_summary(&word, n, images.len(), assigning).into());
refresh_keywords(window, ctl, &images);
start_xmp_writes(window, ctl, &images);
@@ -268,7 +278,9 @@ fn apply_judgement(
if images.is_empty() {
// Nothing selected. Said out loud rather than ignored: a keystroke
// that silently does nothing reads as a broken key.
window.set_library_status("Select an image first".into());
window
.global::<Library>()
.set_library_status("Select an image first".into());
return;
}
@@ -287,14 +299,18 @@ fn apply_judgement(
};
if let Err(e) = wrote {
window.set_library_error(format!("recording rating: {e}").into());
window
.global::<Library>()
.set_library_error(format!("recording rating: {e}").into());
return;
}
// Report what happened, in the user's terms rather than as a count of
// rows. A bulk judgement on a selection is easy to trigger by accident
// and the status line is the only confirmation of its extent.
window.set_library_status(judgement_summary(images.len(), rating, flag).into());
window
.global::<Library>()
.set_library_status(judgement_summary(images.len(), rating, flag).into());
// Refresh the grid and the chips from what actually landed, rather
// than assuming the write took: a clamped or coalesced value must show
@@ -363,7 +379,9 @@ pub fn paste_settings_to_selection(
if images.is_empty() {
// Said out loud rather than ignored, matching what a judgement
// keystroke does with an empty selection.
window.set_library_status("Select an image first".into());
window
.global::<Library>()
.set_library_status("Select an image first".into());
return;
}
@@ -383,12 +401,14 @@ pub fn paste_settings_to_selection(
};
if writes.is_empty() {
window.set_library_error("Could not find those images in the catalog.".into());
window
.global::<Library>()
.set_library_error("Could not find those images in the catalog.".into());
return;
}
let count = writes.len();
window.set_library_status(
window.global::<Library>().set_library_status(
format!(
"Applied settings to {count} image{}.",
if count == 1 { "" } else { "s" }
@@ -643,7 +663,7 @@ fn drain_xmp(
}
}
if let Some(w) = weak.upgrade() {
w.set_library_status(status.into());
w.global::<Library>().set_library_status(status.into());
if reload {
// The grid draws what the reload changed, and the
// settings page stops offering what is settled.
@@ -752,7 +772,7 @@ pub(crate) fn start_sidecar_writes(
log::debug!("{queued} sidecar(s) queued for upload");
ctl_cb.outbox_maybe_dirty.set(true);
job.finish_quietly();
w.set_library_status(
w.global::<Library>().set_library_status(
format!("{queued} edit(s) saved · will upload when back online").into(),
);
stop(&ctl_cb.sidecar_timer);
@@ -772,7 +792,7 @@ pub(crate) fn start_sidecar_writes(
// Said plainly, because the consequence is specific:
// the rating is safe in the catalog but will not
// survive deleting it.
w.set_library_status(
w.global::<Library>().set_library_status(
format!(
"{written} saved · {failed} could not be written \
to the library folder"