Merge: the collections, identity, merge, launch and import wiring lifted into named functions
This commit is contained in:
+13
-13
File diff suppressed because one or more lines are too long
+166
-47
@@ -1672,17 +1672,52 @@ pub fn wire<S, R, P, C>(
|
|||||||
P: Fn(usize, usize) -> Vec<ImageId> + 'static,
|
P: Fn(usize, usize) -> Vec<ImageId> + 'static,
|
||||||
C: Fn() -> Option<dr_sync::Connection> + 'static,
|
C: Fn() -> Option<dr_sync::Connection> + 'static,
|
||||||
{
|
{
|
||||||
// Coerced to trait objects here rather than at each use: `start_trash` and
|
// Coerced to trait objects here rather than at each use: each is shared by
|
||||||
// `drain_trash` are shared by three callbacks, and a generic parameter would
|
// several of the functions below, and a generic parameter would make every
|
||||||
// make each of them a separate instantiation for no gain.
|
// one of them a separate instantiation for no gain.
|
||||||
let on_scope_changed: Rc<dyn Fn()> = Rc::new(on_scope_changed);
|
let on_scope_changed: Rc<dyn Fn()> = Rc::new(on_scope_changed);
|
||||||
let visible_ids = Rc::new(visible_ids);
|
let visible_ids: Rc<dyn Fn() -> Vec<ImageId>> = Rc::new(visible_ids);
|
||||||
// A shift-click asks the catalog what lies between its two ends, and the
|
// A shift-click asks the catalog what lies between its two ends, and the
|
||||||
// catalog belongs to the grid's controller — see `span_source`.
|
// catalog belongs to the grid's controller — see `span_source`.
|
||||||
*ctl.span_source.borrow_mut() = Some(Rc::new(span_ids));
|
*ctl.span_source.borrow_mut() = Some(Rc::new(span_ids));
|
||||||
let session: Rc<dyn Fn() -> Option<dr_sync::Connection>> = Rc::new(session);
|
let session: Rc<dyn Fn() -> Option<dr_sync::Connection>> = Rc::new(session);
|
||||||
|
|
||||||
// --- selection ---------------------------------------------------------
|
wire_selection(window, &ctl, &catalog, &visible_ids, &on_scope_changed);
|
||||||
|
wire_selection_filing(window, &ctl, &catalog, &visible_ids, &on_scope_changed);
|
||||||
|
wire_drag(
|
||||||
|
window,
|
||||||
|
&ctl,
|
||||||
|
&catalog,
|
||||||
|
&visible_ids,
|
||||||
|
&on_scope_changed,
|
||||||
|
&session,
|
||||||
|
);
|
||||||
|
wire_trash(window, &ctl, &catalog, &session, &on_scope_changed);
|
||||||
|
wire_trash_from_grid(
|
||||||
|
window,
|
||||||
|
&ctl,
|
||||||
|
&catalog,
|
||||||
|
&session,
|
||||||
|
&on_scope_changed,
|
||||||
|
&visible_ids,
|
||||||
|
);
|
||||||
|
wire_tree_navigation(window, &ctl, &catalog, &on_scope_changed);
|
||||||
|
wire_create(window, &ctl, &catalog);
|
||||||
|
wire_rename(window, &ctl, &catalog);
|
||||||
|
wire_remove(window, &ctl, &catalog, &visible_ids, &on_scope_changed);
|
||||||
|
wire_row_drag(window, &ctl, &catalog);
|
||||||
|
wire_row_menu(window, &ctl, &catalog, &on_scope_changed, &visible_ids);
|
||||||
|
wire_membership(window, &ctl, &catalog, &visible_ids, &on_scope_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selection.
|
||||||
|
fn wire_selection(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -1882,7 +1917,17 @@ pub fn wire<S, R, P, C>(
|
|||||||
sync_selection(&w, &ctl, &ids);
|
sync_selection(&w, &ctl, &ids);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selection, continued: turning it into a collection, toggling the
|
||||||
|
/// mode, and filing it into one that already exists.
|
||||||
|
fn wire_selection_filing(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
// TRACES: FR-CAT-5
|
// TRACES: FR-CAT-5
|
||||||
// A collection holding exactly what is selected.
|
// A collection holding exactly what is selected.
|
||||||
//
|
//
|
||||||
@@ -2056,12 +2101,20 @@ pub fn wire<S, R, P, C>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- drag --------------------------------------------------------------
|
/// Drag.
|
||||||
//
|
///
|
||||||
// Slint owns the gesture (see the preamble). What is left here is the
|
/// Slint owns the gesture (see the preamble). What is left here is the
|
||||||
// payload — the image ids the drop will act on — and the spring.
|
/// payload — the image ids the drop will act on — and the spring.
|
||||||
|
fn wire_drag(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
session: &Rc<dyn Fn() -> Option<dr_sync::Connection>>,
|
||||||
|
) {
|
||||||
// TRACES: FR-CAT-7
|
// TRACES: FR-CAT-7
|
||||||
// **What arms the drag, not what it carries.**
|
// **What arms the drag, not what it carries.**
|
||||||
//
|
//
|
||||||
@@ -2335,13 +2388,21 @@ pub fn wire<S, R, P, C>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- trash -------------------------------------------------------------
|
/// Trash.
|
||||||
//
|
///
|
||||||
// TRACES: FR-CAT-15
|
/// TRACES: FR-CAT-15
|
||||||
// A drop here is a *soft delete*: the file moves to a trash folder on the
|
/// A drop here is a *soft delete*: the file moves to a trash folder on the
|
||||||
// server and the catalog records where it came from. Nothing is destroyed
|
/// server and the catalog records where it came from. Nothing is destroyed
|
||||||
// until the user empties it, which is a separate, deliberate action.
|
/// until the user empties it, which is a separate, deliberate action.
|
||||||
|
fn wire_trash(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
session: &Rc<dyn Fn() -> Option<dr_sync::Connection>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -2379,19 +2440,27 @@ pub fn wire<S, R, P, C>(
|
|||||||
start_restore(&w, &ctl, &catalog, &session, &chosen, &reload);
|
start_restore(&w, &ctl, &catalog, &session, &chosen, &reload);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- trash from the grid ----------------------------------------------
|
/// Trash from the grid.
|
||||||
//
|
///
|
||||||
// Until now the only route to the trash was dragging onto the sidebar row.
|
/// Until now the only route to the trash was dragging onto the sidebar row.
|
||||||
// These two are the direct gestures: the trash target on a cell's rating
|
/// These two are the direct gestures: the trash target on a cell's rating
|
||||||
// strip, and the `Delete` key.
|
/// strip, and the `Delete` key.
|
||||||
//
|
///
|
||||||
// Both land here rather than in `library_ui` because everything the
|
/// Both land here rather than in `library_ui` because everything the
|
||||||
// operation needs — the selection, the session closure, `start_trash` and
|
/// operation needs — the selection, the session closure, `start_trash` and
|
||||||
// its drain — already lives in this module. Reaching them from the grid
|
/// its drain — already lives in this module. Reaching them from the grid
|
||||||
// side would mean either duplicating the worker plumbing or moving it, and
|
/// side would mean either duplicating the worker plumbing or moving it, and
|
||||||
// trash is one feature whichever component happens to trigger it.
|
/// trash is one feature whichever component happens to trigger it.
|
||||||
|
fn wire_trash_from_grid(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
session: &Rc<dyn Fn() -> Option<dr_sync::Connection>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
) {
|
||||||
// The trash glyph on one cell. Acts on that photograph alone: the pointer
|
// The trash glyph on one cell. Acts on that photograph alone: the pointer
|
||||||
// named it, and a click that silently trashed an entire selection would be
|
// named it, and a click that silently trashed an entire selection would be
|
||||||
// exactly the trap the strip's other targets are laid out to avoid.
|
// exactly the trap the strip's other targets are laid out to avoid.
|
||||||
@@ -2494,8 +2563,15 @@ pub fn wire<S, R, P, C>(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- tree navigation ---------------------------------------------------
|
/// Tree navigation.
|
||||||
|
fn wire_tree_navigation(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -2554,8 +2630,14 @@ pub fn wire<S, R, P, C>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- create ------------------------------------------------------------
|
/// Create.
|
||||||
|
fn wire_create(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -2574,14 +2656,19 @@ pub fn wire<S, R, P, C>(
|
|||||||
create_child(&w, &ctl, cat, *ctl.scope.borrow());
|
create_child(&w, &ctl, cat, *ctl.scope.borrow());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- rename ------------------------------------------------------------
|
/// Rename.
|
||||||
//
|
///
|
||||||
// Inline in the row, opened by a double-click or `F2`. The gesture is worth
|
/// Inline in the row, opened by a double-click or `F2`. The gesture is worth
|
||||||
// the field rather than a dialog: renaming is how a hierarchy gets tidied,
|
/// the field rather than a dialog: renaming is how a hierarchy gets tidied,
|
||||||
// and it is done in runs of several — a modal per collection would make
|
/// and it is done in runs of several — a modal per collection would make
|
||||||
// that a chore.
|
/// that a chore.
|
||||||
|
fn wire_rename(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -2656,8 +2743,16 @@ pub fn wire<S, R, P, C>(
|
|||||||
w.set_collection_error(slint::SharedString::new());
|
w.set_collection_error(slint::SharedString::new());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- remove from the collection being shown ---------------------------
|
/// Remove from the collection being shown.
|
||||||
|
fn wire_remove(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -2697,7 +2792,15 @@ pub fn wire<S, R, P, C>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tree row's own hold-drag: lift a row, drag it onto another to
|
||||||
|
/// reparent, or onto the root to un-nest it.
|
||||||
|
fn wire_row_drag(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
) {
|
||||||
// TRACES: FR-CAT-7 | FR-UI-2 | FR-UI-3 | FR-UI-4
|
// TRACES: FR-CAT-7 | FR-UI-2 | FR-UI-3 | FR-UI-4
|
||||||
// The hold on a sidebar row: it arms the drag that rearranges the tree,
|
// The hold on a sidebar row: it arms the drag that rearranges the tree,
|
||||||
// and it opens the row menu. Which one the user gets is decided on release
|
// and it opens the row menu. Which one the user gets is decided on release
|
||||||
@@ -2836,15 +2939,23 @@ pub fn wire<S, R, P, C>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- the row menu ------------------------------------------------------
|
/// The row menu.
|
||||||
//
|
///
|
||||||
// Right-click, or a long press on touch. This gesture used to *delete an
|
/// Right-click, or a long press on touch. This gesture used to *delete an
|
||||||
// empty collection outright* and refuse with an error message otherwise,
|
/// empty collection outright* and refuse with an error message otherwise,
|
||||||
// which was wrong in both directions at once: the destructive half fired
|
/// which was wrong in both directions at once: the destructive half fired
|
||||||
// with no confirmation and nothing on screen said it would, and the
|
/// with no confirmation and nothing on screen said it would, and the
|
||||||
// refusing half meant a collection holding anything could not be deleted
|
/// refusing half meant a collection holding anything could not be deleted
|
||||||
// at all — the user emptied it by hand and then did the same thing.
|
/// at all — the user emptied it by hand and then did the same thing.
|
||||||
|
fn wire_row_menu(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -3044,8 +3155,16 @@ pub fn wire<S, R, P, C>(
|
|||||||
close_row_menu(&w, &ctl);
|
close_row_menu(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- where the selection is filed --------------------------------------
|
/// Where the selection is filed.
|
||||||
|
fn wire_membership(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<CollectionsController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
visible_ids: &Rc<dyn Fn() -> Vec<ImageId>>,
|
||||||
|
on_scope_changed: &Rc<dyn Fn()>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
|
|||||||
+156
-40
@@ -582,6 +582,20 @@ fn push_grouping(window: &AppWindow, settings: &crate::settings_ui::SettingsCont
|
|||||||
window.set_identity_min_group_size(s.faces.min_group_size as i32);
|
window.set_identity_min_group_size(s.faces.min_group_size as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! reload {
|
||||||
|
($w:expr, $ctl:expr, $catalog:expr, $store:expr, $settings:expr, $eyes:expr, $changed:ident) => {
|
||||||
|
refresh(
|
||||||
|
&$w,
|
||||||
|
&$ctl,
|
||||||
|
&$catalog,
|
||||||
|
$store(),
|
||||||
|
&model_id(&$settings),
|
||||||
|
$eyes(),
|
||||||
|
Changed::$changed,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Attach every Identity callback.
|
/// Attach every Identity callback.
|
||||||
///
|
///
|
||||||
/// Eight arguments because the screen has eight distinct dependencies and no
|
/// Eight arguments because the screen has eight distinct dependencies and no
|
||||||
@@ -616,10 +630,69 @@ pub fn wire<S, M, P>(
|
|||||||
let models: Rc<dyn Fn() -> Option<ModelPaths>> = Rc::new(models);
|
let models: Rc<dyn Fn() -> Option<ModelPaths>> = Rc::new(models);
|
||||||
let paths: Rc<dyn Fn() -> Option<SweepPaths>> = Rc::new(paths);
|
let paths: Rc<dyn Fn() -> Option<SweepPaths>> = Rc::new(paths);
|
||||||
|
|
||||||
// The dials start where the settings file left them, once, rather than on
|
// Re-read everything and redraw. Every mutating callback ends in this
|
||||||
// every open: the screen writes them back through the two callbacks below,
|
// rather than patching the model in place: the operations here have
|
||||||
// and re-pushing them mid-drag would fight the slider's own live value.
|
// second-order effects — a merge empties a person, a split creates one,
|
||||||
push_grouping(window, &settings);
|
// a rejection changes two counts — and a model patched by hand would
|
||||||
|
// drift from the catalog in exactly the cases that matter.
|
||||||
|
// Whether the eye models are on this machine, asked each time rather
|
||||||
|
// than once: it is two `is_file` checks, and a user who drops the files
|
||||||
|
// in while the app is open should see the faces become work to measure.
|
||||||
|
let eyes_available: Rc<dyn Fn() -> bool> = {
|
||||||
|
let models = models.clone();
|
||||||
|
Rc::new(move || models().is_some_and(|m| m.eyes.is_some()))
|
||||||
|
};
|
||||||
|
// The availability closure is an argument rather than named in the
|
||||||
|
// body: macro hygiene would bind the name to this scope's `Rc`, which the
|
||||||
|
// first `move` closure would then take with it.
|
||||||
|
// The last argument is a `Changed` variant, named bare so the call
|
||||||
|
// stays on one line.
|
||||||
|
|
||||||
|
wire_dials(window, &settings);
|
||||||
|
|
||||||
|
wire_navigation(
|
||||||
|
window,
|
||||||
|
&ctl,
|
||||||
|
&catalog,
|
||||||
|
&store,
|
||||||
|
&models,
|
||||||
|
&settings,
|
||||||
|
&eyes_available,
|
||||||
|
);
|
||||||
|
wire_rename_and_merge(window, &ctl, &catalog, &store, &settings, &eyes_available);
|
||||||
|
wire_face_actions(window, &ctl, &catalog, &store, &settings, &eyes_available);
|
||||||
|
wire_grouping_preview(window, &ctl, &paths, &settings);
|
||||||
|
wire_recluster(
|
||||||
|
window,
|
||||||
|
&ctl,
|
||||||
|
&catalog,
|
||||||
|
&store,
|
||||||
|
&paths,
|
||||||
|
&settings,
|
||||||
|
&eyes_available,
|
||||||
|
);
|
||||||
|
wire_indexing(
|
||||||
|
window,
|
||||||
|
&ctl,
|
||||||
|
&catalog,
|
||||||
|
&activity,
|
||||||
|
&store,
|
||||||
|
&models,
|
||||||
|
&paths,
|
||||||
|
&gpu,
|
||||||
|
&settings,
|
||||||
|
&eyes_available,
|
||||||
|
);
|
||||||
|
wire_coverage_and_ignore(window, &ctl, &catalog, &store, &settings, &eyes_available);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The dials.
|
||||||
|
///
|
||||||
|
/// The dials start where the settings file left them, once, rather than on
|
||||||
|
/// every open: the screen writes them back through the two callbacks below,
|
||||||
|
/// and re-pushing them mid-drag would fight the slider's own live value.
|
||||||
|
fn wire_dials(window: &AppWindow, settings: &Rc<crate::settings_ui::SettingsController>) {
|
||||||
|
push_grouping(window, settings);
|
||||||
|
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
@@ -645,38 +718,18 @@ pub fn wire<S, M, P>(
|
|||||||
w.set_identity_grouping_preview(Default::default());
|
w.set_identity_grouping_preview(Default::default());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Re-read everything and redraw. Every mutating callback ends in this
|
/// Opening and closing the screen, and switching which person is shown.
|
||||||
// rather than patching the model in place: the operations here have
|
fn wire_navigation(
|
||||||
// second-order effects — a merge empties a person, a split creates one,
|
window: &AppWindow,
|
||||||
// a rejection changes two counts — and a model patched by hand would
|
ctl: &Rc<IdentityController>,
|
||||||
// drift from the catalog in exactly the cases that matter.
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
// Whether the eye models are on this machine, asked each time rather
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
// than once: it is two `is_file` checks, and a user who drops the files
|
models: &Rc<dyn Fn() -> Option<ModelPaths>>,
|
||||||
// in while the app is open should see the faces become work to measure.
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
let eyes_available: Rc<dyn Fn() -> bool> = {
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
let models = models.clone();
|
) {
|
||||||
Rc::new(move || models().is_some_and(|m| m.eyes.is_some()))
|
|
||||||
};
|
|
||||||
// The availability closure is an argument rather than named in the
|
|
||||||
// body: macro hygiene would bind the name to this scope's `Rc`, which the
|
|
||||||
// first `move` closure would then take with it.
|
|
||||||
// The last argument is a `Changed` variant, named bare so the call
|
|
||||||
// stays on one line.
|
|
||||||
macro_rules! reload {
|
|
||||||
($w:expr, $ctl:expr, $catalog:expr, $store:expr, $settings:expr, $eyes:expr, $changed:ident) => {
|
|
||||||
refresh(
|
|
||||||
&$w,
|
|
||||||
&$ctl,
|
|
||||||
&$catalog,
|
|
||||||
$store(),
|
|
||||||
&model_id(&$settings),
|
|
||||||
$eyes(),
|
|
||||||
Changed::$changed,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let eyes_available = eyes_available.clone();
|
let eyes_available = eyes_available.clone();
|
||||||
@@ -742,7 +795,17 @@ pub fn wire<S, M, P>(
|
|||||||
reset_name_field(&w);
|
reset_name_field(&w);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renaming a person, and the namesake merge offer a rename can raise.
|
||||||
|
fn wire_rename_and_merge(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let eyes_available = eyes_available.clone();
|
let eyes_available = eyes_available.clone();
|
||||||
@@ -850,7 +913,17 @@ pub fn wire<S, M, P>(
|
|||||||
clear_merge_offer(&w, &ctl);
|
clear_merge_offer(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The grid: picking, confirming, rejecting and splitting off faces.
|
||||||
|
fn wire_face_actions(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let eyes_available = eyes_available.clone();
|
let eyes_available = eyes_available.clone();
|
||||||
@@ -970,7 +1043,15 @@ pub fn wire<S, M, P>(
|
|||||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dry run of the grouping dials, without writing anything.
|
||||||
|
fn wire_grouping_preview(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
paths: &Rc<dyn Fn() -> Option<SweepPaths>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -1033,7 +1114,18 @@ pub fn wire<S, M, P>(
|
|||||||
park_preview_timer(timer);
|
park_preview_timer(timer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regroup: rerun clustering over faces already indexed.
|
||||||
|
fn wire_recluster(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
|
paths: &Rc<dyn Fn() -> Option<SweepPaths>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -1135,12 +1227,26 @@ pub fn wire<S, M, P>(
|
|||||||
park_timer(timer);
|
park_timer(timer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// One launcher behind two buttons. "Index faces" and "Re-index every
|
#[allow(clippy::too_many_arguments)]
|
||||||
// face" differ only in which images the pass visits (`FaceSweepScope`);
|
/// One launcher behind two buttons. "Index faces" and "Re-index every
|
||||||
// the models, the progress, the activity row and the Stop button are the
|
/// face" differ only in which images the pass visits (`FaceSweepScope`);
|
||||||
// same, and a second copy of this closure would be a second place for
|
/// the models, the progress, the activity row and the Stop button are the
|
||||||
// them to disagree.
|
/// same, and a second copy of this closure would be a second place for
|
||||||
|
/// them to disagree.
|
||||||
|
fn wire_indexing(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
activity: &Rc<crate::activity::ActivityLog>,
|
||||||
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
|
models: &Rc<dyn Fn() -> Option<ModelPaths>>,
|
||||||
|
paths: &Rc<dyn Fn() -> Option<SweepPaths>>,
|
||||||
|
gpu: &Rc<Option<dr_gpu::GpuContext>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
|
) {
|
||||||
let launch: Rc<dyn Fn(crate::repairs::Scope)> = {
|
let launch: Rc<dyn Fn(crate::repairs::Scope)> = {
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -1347,7 +1453,17 @@ pub fn wire<S, M, P>(
|
|||||||
reload!(w, ctl, catalog, store, settings, eyes_available, Library);
|
reload!(w, ctl, catalog, store, settings, eyes_available, Library);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage, setting a person aside, and clearing every person and face.
|
||||||
|
fn wire_coverage_and_ignore(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<IdentityController>,
|
||||||
|
catalog: &Rc<RefCell<Option<Catalog>>>,
|
||||||
|
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
|
||||||
|
settings: &Rc<crate::settings_ui::SettingsController>,
|
||||||
|
eyes_available: &Rc<dyn Fn() -> bool>,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
|
|||||||
@@ -310,7 +310,17 @@ where
|
|||||||
let context = Rc::new(context);
|
let context = Rc::new(context);
|
||||||
let on_imported = Rc::new(on_imported);
|
let on_imported = Rc::new(on_imported);
|
||||||
|
|
||||||
// --- opening and closing ---------------------------------------------
|
wire_opening_and_closing(window, &ctl, &context);
|
||||||
|
wire_choosing_a_source(window, &ctl, &context);
|
||||||
|
wire_options(window, &ctl);
|
||||||
|
wire_running(window, &ctl, &context, &on_imported);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opening and closing.
|
||||||
|
fn wire_opening_and_closing<C>(window: &AppWindow, ctl: &Rc<ImportController>, context: &Rc<C>)
|
||||||
|
where
|
||||||
|
C: Fn() -> Option<Context> + 'static,
|
||||||
|
{
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -351,8 +361,13 @@ where
|
|||||||
w.set_show_import(false);
|
w.set_show_import(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- choosing a source -------------------------------------------------
|
/// Choosing a source.
|
||||||
|
fn wire_choosing_a_source<C>(window: &AppWindow, ctl: &Rc<ImportController>, context: &Rc<C>)
|
||||||
|
where
|
||||||
|
C: Fn() -> Option<Context> + 'static,
|
||||||
|
{
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -395,8 +410,10 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- options -----------------------------------------------------------
|
/// Options.
|
||||||
|
fn wire_options(window: &AppWindow, ctl: &Rc<ImportController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -465,8 +482,18 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- running -----------------------------------------------------------
|
/// Running.
|
||||||
|
fn wire_running<C, F>(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<ImportController>,
|
||||||
|
context: &Rc<C>,
|
||||||
|
on_imported: &Rc<F>,
|
||||||
|
) where
|
||||||
|
C: Fn() -> Option<Context> + 'static,
|
||||||
|
F: Fn() + 'static,
|
||||||
|
{
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
|
|||||||
+44
-16
@@ -91,7 +91,19 @@ pub fn wire<F>(window: &AppWindow, controller: Rc<LaunchController>, on_open_lib
|
|||||||
where
|
where
|
||||||
F: Fn(Account) + 'static,
|
F: Fn(Account) + 'static,
|
||||||
{
|
{
|
||||||
// --- sign in -------------------------------------------------------
|
wire_sign_in(window, &controller);
|
||||||
|
wire_use_folder(window, &controller);
|
||||||
|
wire_sign_out(window, &controller);
|
||||||
|
wire_formats(window, &controller);
|
||||||
|
wire_choose_folder_and_open(window, &controller, on_open_library);
|
||||||
|
wire_folder_picker_navigation(window, &controller);
|
||||||
|
wire_copy_url(window, &controller);
|
||||||
|
|
||||||
|
render(window, &controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign in: the browser flow, and the app-password fallback.
|
||||||
|
fn wire_sign_in(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -117,7 +129,7 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- sign in with an app password -----------------------------------
|
// Sign in with an app password.
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -144,13 +156,15 @@ where
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- use a folder ---------------------------------------------------
|
/// Use a folder.
|
||||||
//
|
///
|
||||||
// No thread, no waiting state, no credential: the whole sign-in is a
|
/// No thread, no waiting state, no credential: the whole sign-in is a
|
||||||
// `stat`. That asymmetry with the browser flow above is not a special
|
/// `stat`. That asymmetry with the browser flow above is not a special
|
||||||
// case in the screen — it is what [`SignIn::EndpointOnly`] means, and any
|
/// case in the screen — it is what [`SignIn::EndpointOnly`] means, and any
|
||||||
// future connector declaring it lands here rather than in new code.
|
/// future connector declaring it lands here rather than in new code.
|
||||||
|
fn wire_use_folder(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -166,8 +180,10 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- sign out ------------------------------------------------------
|
/// Sign out.
|
||||||
|
fn wire_sign_out(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -186,8 +202,10 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- format tick-boxes ---------------------------------------------
|
/// Format tick-boxes.
|
||||||
|
fn wire_formats(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -210,8 +228,16 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- choose folder --------------------------------------------------
|
/// Choose folder, and open library.
|
||||||
|
fn wire_choose_folder_and_open<F>(
|
||||||
|
window: &AppWindow,
|
||||||
|
controller: &Rc<LaunchController>,
|
||||||
|
on_open_library: F,
|
||||||
|
) where
|
||||||
|
F: Fn(Account) + 'static,
|
||||||
|
{
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -223,7 +249,7 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- open library ---------------------------------------------------
|
// Open library.
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -236,8 +262,10 @@ where
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- folder picker navigation ---------------------------------------
|
/// Folder picker navigation.
|
||||||
|
fn wire_folder_picker_navigation(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
@@ -296,8 +324,10 @@ where
|
|||||||
render(&w, &ctl);
|
render(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- copy the login URL ---------------------------------------------
|
/// Copy the login URL.
|
||||||
|
fn wire_copy_url(window: &AppWindow, controller: &Rc<LaunchController>) {
|
||||||
{
|
{
|
||||||
let ctl = controller.clone();
|
let ctl = controller.clone();
|
||||||
window.on_launch_copy_url(move || {
|
window.on_launch_copy_url(move || {
|
||||||
@@ -310,8 +340,6 @@ where
|
|||||||
log::info!("login url: {url}");
|
log::info!("login url: {url}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(window, &controller);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-NC-13
|
/// TRACES: FR-NC-13
|
||||||
|
|||||||
@@ -92,11 +92,28 @@ pub fn wire<S, C, F>(
|
|||||||
S: Fn() -> Vec<crate::export::Source> + 'static,
|
S: Fn() -> Vec<crate::export::Source> + 'static,
|
||||||
C: Fn() -> Option<Context> + 'static,
|
C: Fn() -> Option<Context> + 'static,
|
||||||
F: Fn(&AppWindow) + 'static,
|
F: Fn(&AppWindow) + 'static,
|
||||||
|
{
|
||||||
|
wire_start(window, &ctl, gpu, sources, context, on_done);
|
||||||
|
wire_decision(window, &ctl);
|
||||||
|
wire_stop_and_leave(window, &ctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start.
|
||||||
|
fn wire_start<S, C, F>(
|
||||||
|
window: &AppWindow,
|
||||||
|
ctl: &Rc<MergeController>,
|
||||||
|
gpu: Option<dr_gpu::GpuContext>,
|
||||||
|
sources: S,
|
||||||
|
context: C,
|
||||||
|
on_done: F,
|
||||||
|
) where
|
||||||
|
S: Fn() -> Vec<crate::export::Source> + 'static,
|
||||||
|
C: Fn() -> Option<Context> + 'static,
|
||||||
|
F: Fn(&AppWindow) + 'static,
|
||||||
{
|
{
|
||||||
let on_done = Rc::new(on_done);
|
let on_done = Rc::new(on_done);
|
||||||
let gpu_for_start = gpu.clone();
|
let gpu_for_start = gpu.clone();
|
||||||
|
|
||||||
// --- start -----------------------------------------------------------
|
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -243,7 +260,7 @@ pub fn wire<S, C, F>(
|
|||||||
};
|
};
|
||||||
start(
|
start(
|
||||||
window,
|
window,
|
||||||
&ctl,
|
ctl,
|
||||||
gpu,
|
gpu,
|
||||||
names,
|
names,
|
||||||
MergeDestination::Local(dir),
|
MergeDestination::Local(dir),
|
||||||
@@ -265,8 +282,10 @@ pub fn wire<S, C, F>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- the decision ----------------------------------------------------
|
/// The decision.
|
||||||
|
fn wire_decision(window: &AppWindow, ctl: &Rc<MergeController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
@@ -360,8 +379,10 @@ pub fn wire<S, C, F>(
|
|||||||
w.set_merge_stage("Filling the preview".into());
|
w.set_merge_stage("Filling the preview".into());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- stopping and leaving --------------------------------------------
|
/// Stopping and leaving.
|
||||||
|
fn wire_stop_and_leave(window: &AppWindow, ctl: &Rc<MergeController>) {
|
||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user