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:
@@ -16,7 +16,7 @@ use dr_types::{CollectionId, ImageId};
|
||||
use slint::{ComponentHandle, Model as _};
|
||||
|
||||
use crate::library;
|
||||
use crate::AppWindow;
|
||||
use crate::{AppWindow, Collections};
|
||||
|
||||
use super::controller::{decide_drop, CollectionsController, Drop};
|
||||
use super::drag::{arm_hold, arm_spring, collapse_spring_opened, compose_drag_image};
|
||||
@@ -213,7 +213,9 @@ fn wire_selection(
|
||||
let current = match library::read_member_order(cat, scope) {
|
||||
Ok(current) => current,
|
||||
Err(e) => {
|
||||
w.set_collection_error(format!("reading the collection's order: {e}").into());
|
||||
w.global::<Collections>().set_collection_error(
|
||||
format!("reading the collection's order: {e}").into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -221,7 +223,8 @@ fn wire_selection(
|
||||
|
||||
match coll::set_order(cat.connection(), scope, &wanted) {
|
||||
Ok(()) => {
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
w.set_library_status(
|
||||
format!(
|
||||
"{} photograph{} moved",
|
||||
@@ -240,7 +243,8 @@ fn wire_selection(
|
||||
}
|
||||
Err(e) => {
|
||||
drop(borrow);
|
||||
w.set_collection_error(format!("reordering: {e}").into());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(format!("reordering: {e}").into());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -339,7 +343,8 @@ fn wire_selection_filing(
|
||||
|
||||
let borrow = catalog.borrow();
|
||||
let Some(cat) = borrow.as_ref() else {
|
||||
w.set_collection_error("Open a library first.".into());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error("Open a library first.".into());
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -361,7 +366,8 @@ fn wire_selection_filing(
|
||||
|
||||
match made {
|
||||
Ok(added) => {
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
w.set_library_status(
|
||||
format!(
|
||||
"{added} photograph{} in {name}",
|
||||
@@ -386,7 +392,8 @@ fn wire_selection_filing(
|
||||
}
|
||||
Err(e) => {
|
||||
drop(borrow);
|
||||
w.set_collection_error(format!("making a collection: {e}").into());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(format!("making a collection: {e}").into());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -449,7 +456,8 @@ fn wire_selection_filing(
|
||||
|
||||
match result {
|
||||
Ok(filed) => {
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
// The honest count, as the drop reports it: claiming all
|
||||
// twelve when nine were already there teaches the user to
|
||||
// distrust every number the app shows them.
|
||||
@@ -474,7 +482,9 @@ fn wire_selection_filing(
|
||||
drop(borrow);
|
||||
reload();
|
||||
}
|
||||
Err(e) => w.set_collection_error(format!("filing: {e}").into()),
|
||||
Err(e) => w
|
||||
.global::<Collections>()
|
||||
.set_collection_error(format!("filing: {e}").into()),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -528,11 +538,13 @@ fn wire_drag(
|
||||
// that cares to read it; the drop itself uses the remembered press, since
|
||||
// it is handed only the target.
|
||||
{
|
||||
window.on_collection_drag_payload(move |id| {
|
||||
let mut data = slint::DataTransfer::default();
|
||||
data.set_user_data(Rc::new(CollectionId(id as u64)));
|
||||
data
|
||||
});
|
||||
window
|
||||
.global::<Collections>()
|
||||
.on_collection_drag_payload(move |id| {
|
||||
let mut data = slint::DataTransfer::default();
|
||||
data.set_user_data(Rc::new(CollectionId(id as u64)));
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -608,24 +620,26 @@ fn wire_drag(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let catalog_for_spring = catalog.clone();
|
||||
window.on_collection_drag_over(move |id, over| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let id = CollectionId(id as u64);
|
||||
window
|
||||
.global::<Collections>()
|
||||
.on_collection_drag_over(move |id, over| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let id = CollectionId(id as u64);
|
||||
|
||||
if !over {
|
||||
// Left this row. Cancel its pending expansion rather than
|
||||
// letting it fire over whatever the pointer moved on to.
|
||||
if *ctl.hover_id.borrow() == Some(id) {
|
||||
*ctl.hover_id.borrow_mut() = None;
|
||||
*ctl.spring_timer.borrow_mut() = None;
|
||||
if !over {
|
||||
// Left this row. Cancel its pending expansion rather than
|
||||
// letting it fire over whatever the pointer moved on to.
|
||||
if *ctl.hover_id.borrow() == Some(id) {
|
||||
*ctl.hover_id.borrow_mut() = None;
|
||||
*ctl.spring_timer.borrow_mut() = None;
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
*ctl.hover_id.borrow_mut() = Some(id);
|
||||
let row = ctl.row_ids.borrow().iter().position(|&c| c == id);
|
||||
arm_spring(&w, &ctl, &catalog_for_spring, row);
|
||||
});
|
||||
*ctl.hover_id.borrow_mut() = Some(id);
|
||||
let row = ctl.row_ids.borrow().iter().position(|&c| c == id);
|
||||
arm_spring(&w, &ctl, &catalog_for_spring, row);
|
||||
});
|
||||
}
|
||||
|
||||
// A drop. Slint hit-tested the release and `can-drop` already refused a
|
||||
@@ -636,77 +650,85 @@ fn wire_drag(
|
||||
let catalog = catalog.clone();
|
||||
// No model refresh or reload here on purpose — see the note at the end
|
||||
// of this handler. `drag-finished` owns those.
|
||||
window.on_collection_dropped(move |id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let id = CollectionId(id as u64);
|
||||
let carried = ctl.dragging.borrow().clone();
|
||||
// Taken, not read: a remembered press must be spent by the drop it
|
||||
// belongs to, or a later empty drop — a file dragged in from
|
||||
// outside, say — would move a collection nobody touched.
|
||||
let pressed = ctl.dragging_collection.borrow_mut().take();
|
||||
window
|
||||
.global::<Collections>()
|
||||
.on_collection_dropped(move |id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let id = CollectionId(id as u64);
|
||||
let carried = ctl.dragging.borrow().clone();
|
||||
// Taken, not read: a remembered press must be spent by the drop it
|
||||
// belongs to, or a later empty drop — a file dragged in from
|
||||
// outside, say — would move a collection nobody touched.
|
||||
let pressed = ctl.dragging_collection.borrow_mut().take();
|
||||
|
||||
let carried = match decide_drop(&carried, pressed, id) {
|
||||
Drop::Nothing => return,
|
||||
Drop::Reparent(source) => {
|
||||
let result = {
|
||||
let borrow = catalog.borrow();
|
||||
let Some(cat) = borrow.as_ref() else { return };
|
||||
coll::set_parent(cat.connection(), source, Some(id))
|
||||
};
|
||||
match result {
|
||||
Ok(()) => {
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
w.set_library_status("Moved collection".into());
|
||||
let carried = match decide_drop(&carried, pressed, id) {
|
||||
Drop::Nothing => return,
|
||||
Drop::Reparent(source) => {
|
||||
let result = {
|
||||
let borrow = catalog.borrow();
|
||||
if let Some(cat) = borrow.as_ref() {
|
||||
refresh_tree(&w, &ctl, cat);
|
||||
let Some(cat) = borrow.as_ref() else { return };
|
||||
coll::set_parent(cat.connection(), source, Some(id))
|
||||
};
|
||||
match result {
|
||||
Ok(()) => {
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
w.set_library_status("Moved collection".into());
|
||||
let borrow = catalog.borrow();
|
||||
if let Some(cat) = borrow.as_ref() {
|
||||
refresh_tree(&w, &ctl, cat);
|
||||
}
|
||||
}
|
||||
// The catalog refuses a cycle rather than letting the
|
||||
// tree walk spin. Saying so is better than a drop that
|
||||
// silently does nothing.
|
||||
Err(e) => w
|
||||
.global::<Collections>()
|
||||
.set_collection_error(format!("moving collection: {e}").into()),
|
||||
}
|
||||
// The catalog refuses a cycle rather than letting the
|
||||
// tree walk spin. Saying so is better than a drop that
|
||||
// silently does nothing.
|
||||
Err(e) => w.set_collection_error(format!("moving collection: {e}").into()),
|
||||
return;
|
||||
}
|
||||
return;
|
||||
Drop::FileImages(images) => images,
|
||||
};
|
||||
|
||||
// Scoped so the borrow is released before the spring cleanup below,
|
||||
// which needs the catalog itself.
|
||||
let result = {
|
||||
let borrow = catalog.borrow();
|
||||
let Some(cat) = borrow.as_ref() else { return };
|
||||
coll::add_images(cat.connection(), id, &carried)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(added) => {
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
// "Added 3 of 12" is the honest report when nine were
|
||||
// already there; claiming 12 would teach the user to
|
||||
// distrust the count.
|
||||
let msg = if added == carried.len() {
|
||||
format!("Added {added} to collection")
|
||||
} else {
|
||||
format!(
|
||||
"Added {added} of {} — the rest were already there",
|
||||
carried.len()
|
||||
)
|
||||
};
|
||||
w.set_library_status(msg.into());
|
||||
}
|
||||
Err(e) => w
|
||||
.global::<Collections>()
|
||||
.set_collection_error(format!("adding to collection: {e}").into()),
|
||||
}
|
||||
Drop::FileImages(images) => images,
|
||||
};
|
||||
|
||||
// Scoped so the borrow is released before the spring cleanup below,
|
||||
// which needs the catalog itself.
|
||||
let result = {
|
||||
let borrow = catalog.borrow();
|
||||
let Some(cat) = borrow.as_ref() else { return };
|
||||
coll::add_images(cat.connection(), id, &carried)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(added) => {
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
// "Added 3 of 12" is the honest report when nine were
|
||||
// already there; claiming 12 would teach the user to
|
||||
// distrust the count.
|
||||
let msg = if added == carried.len() {
|
||||
format!("Added {added} to collection")
|
||||
} else {
|
||||
format!(
|
||||
"Added {added} of {} — the rest were already there",
|
||||
carried.len()
|
||||
)
|
||||
};
|
||||
w.set_library_status(msg.into());
|
||||
}
|
||||
Err(e) => w.set_collection_error(format!("adding to collection: {e}").into()),
|
||||
}
|
||||
|
||||
// Recorded, not acted on. Every visible consequence — rebuilding
|
||||
// the tree, refreshing the badges, rereading the grid — happens in
|
||||
// `drag-finished`, because all three replace models that Slint is
|
||||
// *currently walking* to deliver this very event. Tearing down a
|
||||
// live `DropArea` from inside its own `dropped` handler is the same
|
||||
// hazard `sync_rows` in lib.rs documents for the adjust panel.
|
||||
*ctl.dropped_on.borrow_mut() = Some(id);
|
||||
});
|
||||
// Recorded, not acted on. Every visible consequence — rebuilding
|
||||
// the tree, refreshing the badges, rereading the grid — happens in
|
||||
// `drag-finished`, because all three replace models that Slint is
|
||||
// *currently walking* to deliver this very event. Tearing down a
|
||||
// live `DropArea` from inside its own `dropped` handler is the same
|
||||
// hazard `sync_rows` in lib.rs documents for the adjust panel.
|
||||
*ctl.dropped_on.borrow_mut() = Some(id);
|
||||
});
|
||||
}
|
||||
|
||||
// The drag ended: dropped, or abandoned. This is where the consequences of
|
||||
@@ -784,7 +806,7 @@ fn wire_trash(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let session = session.clone();
|
||||
window.on_trash_dropped(move || {
|
||||
window.global::<Collections>().on_trash_dropped(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let carried = ctl.dragging.borrow().clone();
|
||||
if carried.is_empty() {
|
||||
@@ -795,7 +817,8 @@ fn wire_trash(
|
||||
// still being walked to deliver this event.
|
||||
*ctl.trash_requested.borrow_mut() = Some(carried);
|
||||
let _ = session;
|
||||
w.set_collection_error(slint::SharedString::new());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(slint::SharedString::new());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -808,7 +831,7 @@ fn wire_trash(
|
||||
let catalog = catalog.clone();
|
||||
let session = session.clone();
|
||||
let reload = on_scope_changed.clone();
|
||||
window.on_trash_restore(move || {
|
||||
window.global::<Collections>().on_trash_restore(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let chosen = ctl.selected();
|
||||
if chosen.is_empty() {
|
||||
@@ -887,7 +910,7 @@ fn wire_trash_from_grid(
|
||||
let catalog = catalog.clone();
|
||||
let session = session.clone();
|
||||
let reload = on_scope_changed.clone();
|
||||
window.on_trash_empty(move || {
|
||||
window.global::<Collections>().on_trash_empty(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
|
||||
let conn = match session() {
|
||||
@@ -904,7 +927,8 @@ fn wire_trash_from_grid(
|
||||
let listed = match dr_catalog::trash::list(cat.connection(), usize::MAX) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
w.set_collection_error(format!("reading trash: {e}").into());
|
||||
w.global::<Collections>()
|
||||
.set_collection_error(format!("reading trash: {e}").into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user