Move the filter bar and the timeline onto the library's Slint global

The date-range fields and their band on the capture-time axis, the
timeline's bars, labels, scrub/pinch/pan/zoom callbacks and its
sweep/current-bucket/anchored state, and the filter bar's people chips,
mode, eyes-open toggle and gesture reference move from AppWindow onto the
`Library` global. Rust reaches them through window.global::<Library>()
rather than window.set_/get_/on_ on the root, as the earlier commits on
this branch did for the grid's cells, selection, ratings and keywords.
This commit is contained in:
2026-09-20 22:07:12 +02:00
parent 00e2fe6aaf
commit 6bf67cefc4
11 changed files with 488 additions and 424 deletions
+102 -75
View File
@@ -95,8 +95,12 @@ pub(super) fn refresh_timeline(window: &AppWindow, catalog: &Catalog, ctl: &Rc<L
None => {
// No dated images yet. An empty histogram is honest — EXIF is read
// as thumbnails load, so this populates as the user browses.
window.set_library_timeline(slint::ModelRc::new(slint::VecModel::from(vec![])));
window.set_library_timeline_label(slint::SharedString::new());
window
.global::<Library>()
.set_library_timeline(slint::ModelRc::new(slint::VecModel::from(vec![])));
window
.global::<Library>()
.set_library_timeline_label(slint::SharedString::new());
return;
}
};
@@ -206,14 +210,27 @@ pub(super) fn refresh_timeline(window: &AppWindow, catalog: &Catalog, ctl: &Rc<L
buckets.get(i).map(|b| b.start)
})
.unwrap_or(0);
window.set_library_current_bucket(lit as i32);
window.set_library_current_fraction(fraction);
window.set_library_range_from_fraction(band_from);
window.set_library_range_to_fraction(band_to);
window.set_library_timeline_anchored(current.is_some());
window
.global::<Library>()
.set_library_current_bucket(lit as i32);
window
.global::<Library>()
.set_library_current_fraction(fraction);
window
.global::<Library>()
.set_library_range_from_fraction(band_from);
window
.global::<Library>()
.set_library_range_to_fraction(band_to);
window
.global::<Library>()
.set_library_timeline_anchored(current.is_some());
window
.global::<Library>()
.set_library_timeline_label(format!("{} – {}", format_date(from), format_date(to)).into());
window.set_library_timeline(slint::ModelRc::new(slint::VecModel::from(bars)));
window
.global::<Library>()
.set_library_timeline(slint::ModelRc::new(slint::VecModel::from(bars)));
}
/// Whether this bucket size is fine enough for month labels to mean anything.
@@ -904,7 +921,9 @@ fn push_filter(window: &AppWindow, ctl: &Rc<LibraryController>) {
.global::<Library>()
.set_library_local_only(filter.local_only);
ctl.local_only.set(filter.local_only);
window.set_library_filter_eyes_open(filter.eyes_open);
window
.global::<Library>()
.set_library_filter_eyes_open(filter.eyes_open);
push_people_chips(window, ctl);
}
@@ -1019,30 +1038,32 @@ pub(super) fn wire_timeline(window: &AppWindow, ctl: &Rc<LibraryController>) {
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_library_timeline_pan(move |fraction| {
let Some(w) = weak.upgrade() else { return };
let borrow = ctl.catalog.borrow();
let Some(catalog) = borrow.as_ref() else {
return;
};
let Some(full) = catalog_span(catalog, &ctl) else {
return;
};
window
.global::<Library>()
.on_library_timeline_pan(move |fraction| {
let Some(w) = weak.upgrade() else { return };
let borrow = ctl.catalog.borrow();
let Some(catalog) = borrow.as_ref() else {
return;
};
let Some(full) = catalog_span(catalog, &ctl) else {
return;
};
// A fraction of the *visible* span, so dragging half the axis
// moves half a span's worth of time whatever the zoom — and a
// small movement produces a small shift rather than nothing.
let zoom = *ctl.timeline_zoom.borrow();
let (from, to) = zoomed_span(full, zoom, *ctl.timeline_centre.borrow());
let shift = ((to - from) as f64 * fraction as f64) as i64;
if shift == 0 {
return;
}
// A fraction of the *visible* span, so dragging half the axis
// moves half a span's worth of time whatever the zoom — and a
// small movement produces a small shift rather than nothing.
let zoom = *ctl.timeline_zoom.borrow();
let (from, to) = zoomed_span(full, zoom, *ctl.timeline_centre.borrow());
let shift = ((to - from) as f64 * fraction as f64) as i64;
if shift == 0 {
return;
}
let centre = ctl.timeline_centre.borrow().unwrap_or((from + to) / 2) + shift;
*ctl.timeline_centre.borrow_mut() = Some(centre.clamp(full.0, full.1));
refresh_timeline(&w, catalog, &ctl);
});
let centre = ctl.timeline_centre.borrow().unwrap_or((from + to) / 2) + shift;
*ctl.timeline_centre.borrow_mut() = Some(centre.clamp(full.0, full.1));
refresh_timeline(&w, catalog, &ctl);
});
}
// The wheel zooms the axis: a sidebar is a scale, not a list.
@@ -1052,11 +1073,13 @@ pub(super) fn wire_timeline(window: &AppWindow, ctl: &Rc<LibraryController>) {
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_library_timeline_zoom(move |delta| {
if let Some(w) = weak.upgrade() {
apply_zoom(&w, &ctl, delta);
}
});
window
.global::<Library>()
.on_library_timeline_zoom(move |delta| {
if let Some(w) = weak.upgrade() {
apply_zoom(&w, &ctl, delta);
}
});
}
// Dragging the histogram moves the grid through time.
@@ -1067,25 +1090,27 @@ pub(super) fn wire_timeline(window: &AppWindow, ctl: &Rc<LibraryController>) {
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_library_scrub_fraction(move |f| {
let Some(w) = weak.upgrade() else { return };
let borrow = ctl.catalog.borrow();
let Some(catalog) = borrow.as_ref() else {
return;
};
let Some(full) = catalog_span(catalog, &ctl) else {
return;
};
window
.global::<Library>()
.on_library_scrub_fraction(move |f| {
let Some(w) = weak.upgrade() else { return };
let borrow = ctl.catalog.borrow();
let Some(catalog) = borrow.as_ref() else {
return;
};
let Some(full) = catalog_span(catalog, &ctl) else {
return;
};
let (from, to) = zoomed_span(
full,
*ctl.timeline_zoom.borrow(),
*ctl.timeline_centre.borrow(),
);
let when = instant_at((from, to), f);
drop(borrow);
scrub_to(&w, &ctl, when);
});
let (from, to) = zoomed_span(
full,
*ctl.timeline_zoom.borrow(),
*ctl.timeline_centre.borrow(),
);
let when = instant_at((from, to), f);
drop(borrow);
scrub_to(&w, &ctl, when);
});
}
// Pinch, for tablet: no wheel there, so this is the only way to reach the
@@ -1097,30 +1122,32 @@ pub(super) fn wire_timeline(window: &AppWindow, ctl: &Rc<LibraryController>) {
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_library_timeline_pinch(move |ratio| {
let Some(w) = weak.upgrade() else { return };
if !(0.01..=100.0).contains(&ratio) {
return;
}
window
.global::<Library>()
.on_library_timeline_pinch(move |ratio| {
let Some(w) = weak.upgrade() else { return };
if !(0.01..=100.0).contains(&ratio) {
return;
}
let mut accum = ctl.pinch_accum.borrow_mut();
*accum *= ratio;
let mut accum = ctl.pinch_accum.borrow_mut();
*accum *= ratio;
// Whole doublings out of the accumulated ratio, remainder carried.
//
// `log2` rather than repeated halving: one update can carry a
// large ratio — a fast spread, or a trackpad reporting coarsely —
// and stepping once per update would turn an 8× pinch into a
// single level instead of three.
let steps = accum.log2().trunc() as i32;
if steps == 0 {
return;
}
*accum /= (2.0f32).powi(steps);
drop(accum);
// Whole doublings out of the accumulated ratio, remainder carried.
//
// `log2` rather than repeated halving: one update can carry a
// large ratio — a fast spread, or a trackpad reporting coarsely —
// and stepping once per update would turn an 8× pinch into a
// single level instead of three.
let steps = accum.log2().trunc() as i32;
if steps == 0 {
return;
}
*accum /= (2.0f32).powi(steps);
drop(accum);
apply_zoom(&w, &ctl, steps);
});
apply_zoom(&w, &ctl, steps);
});
}
}