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:
+108
-101
@@ -22,7 +22,7 @@ use crate::merge::{
|
||||
self, AlignmentReport, Cancel, Decision, FillSettings, MergeDestination, MergeEvent,
|
||||
MergeInput, MergeRequest,
|
||||
};
|
||||
use crate::{AppWindow, MergeFrameRow, Page};
|
||||
use crate::{AppWindow, Library, MergeFrameRow, Page};
|
||||
|
||||
/// How often the page reads the job's channel.
|
||||
const DRAIN_INTERVAL: std::time::Duration = std::time::Duration::from_millis(120);
|
||||
@@ -118,107 +118,114 @@ fn wire_start<S, C, F>(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let on_done = on_done.clone();
|
||||
window.on_library_merge_selection(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if ctl.job.borrow().is_some() {
|
||||
w.set_library_status("A panorama is already being merged".into());
|
||||
return;
|
||||
}
|
||||
let Some(gpu) = gpu.clone() else {
|
||||
w.set_library_status("No GPU: a panorama cannot be merged on this device".into());
|
||||
return;
|
||||
};
|
||||
let Some(context) = context() else {
|
||||
w.set_library_status("Open a library first".into());
|
||||
return;
|
||||
};
|
||||
// The selection, in library order. The catalog's order is the
|
||||
// grid's, which is capture time — the order a sweep was shot.
|
||||
let sources: Vec<(String, Option<crate::library::CacheContext>)> = sources()
|
||||
.into_iter()
|
||||
.filter_map(|s| match s {
|
||||
crate::export::Source::Library { path, cache } => Some((path, cache)),
|
||||
crate::export::Source::Rendered { .. } => None,
|
||||
})
|
||||
.collect();
|
||||
if sources.len() < 2 {
|
||||
w.set_library_status("Select at least two frames to merge".into());
|
||||
return;
|
||||
}
|
||||
|
||||
// Where the composite goes: beside its sources, through the
|
||||
// outbox (FR-MRG-3). The sources' folder, relative to the root.
|
||||
let root = context.conn.account.root.trim_matches('/').to_string();
|
||||
let first_dir = std::path::Path::new(&sources[0].0)
|
||||
.parent()
|
||||
.map(|p| p.to_string_lossy().trim_matches('/').to_string())
|
||||
.unwrap_or_default();
|
||||
let remote_dir = first_dir
|
||||
.strip_prefix(&root)
|
||||
.map(|s| s.trim_start_matches('/').to_string())
|
||||
.unwrap_or(first_dir);
|
||||
let destination = MergeDestination::Outbox {
|
||||
outbox: context.outbox,
|
||||
remote_dir,
|
||||
};
|
||||
|
||||
let names: Vec<String> = sources
|
||||
.iter()
|
||||
.map(|(p, _)| {
|
||||
std::path::Path::new(p)
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| p.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The fetch, on the job's own thread before the job: every
|
||||
// original comes down in turn — sequential so twelve downloads
|
||||
// do not halve each other's bandwidth — and the job then takes
|
||||
// bytes, which keeps it free of the library.
|
||||
let conn = context.conn.clone();
|
||||
let fetch_names = names.clone();
|
||||
let fetch = move |tx: &Sender<MergeEvent>,
|
||||
cancel: &Cancel|
|
||||
-> Option<Vec<MergeInput>> {
|
||||
let mut frames = Vec::with_capacity(sources.len());
|
||||
for (i, (path, cache)) in sources.into_iter().enumerate() {
|
||||
if cancel.is_cancelled() {
|
||||
let _ = tx.send(MergeEvent::Cancelled);
|
||||
return None;
|
||||
}
|
||||
let _ = tx.send(MergeEvent::Progress {
|
||||
stage: "Fetching",
|
||||
done: i,
|
||||
total: fetch_names.len(),
|
||||
});
|
||||
let rx = crate::library::spawn_full_fetch(conn.clone(), path.clone(), cache);
|
||||
let bytes = match rx.recv() {
|
||||
Ok(Ok(bytes)) => bytes,
|
||||
Ok(Err(e)) => {
|
||||
let _ = tx.send(MergeEvent::Failed(format!(
|
||||
"{}: {}",
|
||||
fetch_names[i], e.message
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = tx.send(MergeEvent::Failed(format!(
|
||||
"{}: the download ended without answering",
|
||||
fetch_names[i]
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
frames.push(MergeInput {
|
||||
name: fetch_names[i].clone(),
|
||||
bytes: std::sync::Arc::new(bytes),
|
||||
});
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_merge_selection(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if ctl.job.borrow().is_some() {
|
||||
w.global::<Library>()
|
||||
.set_library_status("A panorama is already being merged".into());
|
||||
return;
|
||||
}
|
||||
Some(frames)
|
||||
};
|
||||
start(&w, &ctl, gpu, names, destination, fetch, &on_done);
|
||||
});
|
||||
let Some(gpu) = gpu.clone() else {
|
||||
w.global::<Library>().set_library_status(
|
||||
"No GPU: a panorama cannot be merged on this device".into(),
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(context) = context() else {
|
||||
w.global::<Library>()
|
||||
.set_library_status("Open a library first".into());
|
||||
return;
|
||||
};
|
||||
// The selection, in library order. The catalog's order is the
|
||||
// grid's, which is capture time — the order a sweep was shot.
|
||||
let sources: Vec<(String, Option<crate::library::CacheContext>)> = sources()
|
||||
.into_iter()
|
||||
.filter_map(|s| match s {
|
||||
crate::export::Source::Library { path, cache } => Some((path, cache)),
|
||||
crate::export::Source::Rendered { .. } => None,
|
||||
})
|
||||
.collect();
|
||||
if sources.len() < 2 {
|
||||
w.global::<Library>()
|
||||
.set_library_status("Select at least two frames to merge".into());
|
||||
return;
|
||||
}
|
||||
|
||||
// Where the composite goes: beside its sources, through the
|
||||
// outbox (FR-MRG-3). The sources' folder, relative to the root.
|
||||
let root = context.conn.account.root.trim_matches('/').to_string();
|
||||
let first_dir = std::path::Path::new(&sources[0].0)
|
||||
.parent()
|
||||
.map(|p| p.to_string_lossy().trim_matches('/').to_string())
|
||||
.unwrap_or_default();
|
||||
let remote_dir = first_dir
|
||||
.strip_prefix(&root)
|
||||
.map(|s| s.trim_start_matches('/').to_string())
|
||||
.unwrap_or(first_dir);
|
||||
let destination = MergeDestination::Outbox {
|
||||
outbox: context.outbox,
|
||||
remote_dir,
|
||||
};
|
||||
|
||||
let names: Vec<String> = sources
|
||||
.iter()
|
||||
.map(|(p, _)| {
|
||||
std::path::Path::new(p)
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| p.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The fetch, on the job's own thread before the job: every
|
||||
// original comes down in turn — sequential so twelve downloads
|
||||
// do not halve each other's bandwidth — and the job then takes
|
||||
// bytes, which keeps it free of the library.
|
||||
let conn = context.conn.clone();
|
||||
let fetch_names = names.clone();
|
||||
let fetch =
|
||||
move |tx: &Sender<MergeEvent>, cancel: &Cancel| -> Option<Vec<MergeInput>> {
|
||||
let mut frames = Vec::with_capacity(sources.len());
|
||||
for (i, (path, cache)) in sources.into_iter().enumerate() {
|
||||
if cancel.is_cancelled() {
|
||||
let _ = tx.send(MergeEvent::Cancelled);
|
||||
return None;
|
||||
}
|
||||
let _ = tx.send(MergeEvent::Progress {
|
||||
stage: "Fetching",
|
||||
done: i,
|
||||
total: fetch_names.len(),
|
||||
});
|
||||
let rx =
|
||||
crate::library::spawn_full_fetch(conn.clone(), path.clone(), cache);
|
||||
let bytes = match rx.recv() {
|
||||
Ok(Ok(bytes)) => bytes,
|
||||
Ok(Err(e)) => {
|
||||
let _ = tx.send(MergeEvent::Failed(format!(
|
||||
"{}: {}",
|
||||
fetch_names[i], e.message
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = tx.send(MergeEvent::Failed(format!(
|
||||
"{}: the download ended without answering",
|
||||
fetch_names[i]
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
frames.push(MergeInput {
|
||||
name: fetch_names[i].clone(),
|
||||
bytes: std::sync::Arc::new(bytes),
|
||||
});
|
||||
}
|
||||
Some(frames)
|
||||
};
|
||||
start(&w, &ctl, gpu, names, destination, fetch, &on_done);
|
||||
});
|
||||
}
|
||||
|
||||
// A way to land on the page at startup with local files, for looking
|
||||
|
||||
Reference in New Issue
Block a user