Merge: the collections, identity, merge, launch and import wiring lifted into named functions

This commit is contained in:
2026-09-20 19:28:12 +02:00
6 changed files with 435 additions and 124 deletions
File diff suppressed because one or more lines are too long
+166 -47
View File
@@ -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();
+155 -39
View File
@@ -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
// rather than patching the model in place: the operations here have
// second-order effects — a merge empties a person, a split creates one,
// 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.
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,
)
};
} }
/// Opening and closing the screen, and switching which person is shown.
fn wire_navigation(
window: &AppWindow,
ctl: &Rc<IdentityController>,
catalog: &Rc<RefCell<Option<Catalog>>>,
store: &Rc<dyn Fn() -> Option<Rc<ThumbStore>>>,
models: &Rc<dyn Fn() -> Option<ModelPaths>>,
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();
@@ -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();
+31 -4
View File
@@ -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
View File
@@ -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
+25 -4
View File
@@ -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();