Give the collections sidebar its own Slint global

AppWindow carried the sidebar's tree, its row menu, renaming, drag and
drop between rows, the trash row, and the membership sheet as ~40
properties and callbacks on the root component, in the pattern CH-1
describes and the develop screen's globals (Adjustments, Framing, Steps,
...) already replaced. Collections.* in collections.slint now holds that
state, declared next to the MembershipRow struct it and the membership
sheet both use; Rust reaches it through window.global::<Collections>()
instead of window.set_/get_/on_/invoke_ on the root.

collection-selected, collection-select, and collection-offline-menu stay
on AppWindow: library_ui.rs invokes collection-select directly and
registers collection-offline-menu's handler, and lib.rs reads
collection-selected for back-navigation, so moving them would have meant
editing library_ui.rs, which another change on this branch is splitting
into a module directory. collections-visible stays too — it is
lib.rs's panel-layout state, seeded from the saved layout before the
sidebar exists, and collections_ui never touches it. Everything prefixed
library- (the grid's drag, selection and keyword state that the sidebar's
Rust also wires for cross-feature gestures like filing a selection into
a collection) stays on the window as well, since it belongs to the
library screen, not the sidebar.
This commit is contained in:
2026-09-20 21:07:25 +02:00
parent 14ed1dc410
commit f6ff5eabd9
9 changed files with 816 additions and 643 deletions
+18 -9
View File
@@ -12,7 +12,7 @@ use dr_catalog::Catalog;
use dr_types::ImageId;
use slint::ComponentHandle;
use crate::AppWindow;
use crate::{AppWindow, Collections};
use super::controller::CollectionsController;
use super::tree_sync::refresh_tree;
@@ -31,7 +31,9 @@ pub(super) fn start_trash(
reload: &Rc<dyn Fn()>,
) {
let Some(conn) = session() else {
window.set_collection_error("Open a library first.".into());
window
.global::<Collections>()
.set_collection_error("Open a library first.".into());
return;
};
@@ -41,7 +43,9 @@ pub(super) fn start_trash(
match crate::trash::plan_trash(cat, &conn.account.root, images) {
Ok(m) => m,
Err(e) => {
window.set_collection_error(format!("planning delete: {e}").into());
window
.global::<Collections>()
.set_collection_error(format!("planning delete: {e}").into());
return;
}
}
@@ -97,7 +101,9 @@ pub(super) fn start_restore(
reload: &Rc<dyn Fn()>,
) {
let Some(conn) = session() else {
window.set_collection_error("Open a library first.".into());
window
.global::<Collections>()
.set_collection_error("Open a library first.".into());
return;
};
@@ -107,7 +113,9 @@ pub(super) fn start_restore(
match crate::trash::plan_restore(cat, images) {
Ok(m) => m,
Err(e) => {
window.set_collection_error(format!("planning restore: {e}").into());
window
.global::<Collections>()
.set_collection_error(format!("planning restore: {e}").into());
return;
}
}
@@ -116,7 +124,7 @@ pub(super) fn start_restore(
if moves.is_empty() {
// Said out loud rather than passed over in silence: a button that does
// nothing visible reads as broken, and the reason here is specific.
window.set_collection_error(
window.global::<Collections>().set_collection_error(
"Nothing to restore — no record of where these came from.".into(),
);
return;
@@ -227,7 +235,8 @@ pub(super) fn drain_trash(
}
w.set_library_status(status.into());
if let Some(first) = failed.first() {
w.set_collection_error(first.as_str().into());
w.global::<Collections>()
.set_collection_error(first.as_str().into());
}
let borrow = catalog.borrow();
@@ -264,8 +273,8 @@ fn stop_trash(ctl: &Rc<CollectionsController>) {
/// it.
pub fn refresh_trash(window: &AppWindow, catalog: &Catalog) {
let (n, bytes) = dr_catalog::trash::summary(catalog.connection()).unwrap_or((0, 0));
window.set_trash_count(n as i32);
window.set_trash_label(if n == 0 {
window.global::<Collections>().set_trash_count(n as i32);
window.global::<Collections>().set_trash_label(if n == 0 {
slint::SharedString::new()
} else {
format!("{n} · {}", format_bytes(bytes)).into()