Announce a month at the next row it opens, not only if it begins one
A heading was drawn only on a cell that both began a month and began a row, so at seven columns most months were never named, and the one heading on screen — always on the window's first cell — was wrong about every row below it. Worse, two headings drawn on the same cell overprinted each other. Now a row carries a heading whenever its first cell's month is not the one last announced: a month starting mid-row is named on the next row it opens, one row late and right about everything under it.
This commit is contained in:
+87
-29
@@ -2824,38 +2824,17 @@ fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
// Month headings. The grid is ordered by capture time, so without these a
|
||||
// wall of thumbnails gives no sense of *when* you are looking — the
|
||||
// sidebar says it, but only if you consult it.
|
||||
//
|
||||
// Marked on the cell that both begins a month and begins a row: a heading
|
||||
// stranded mid-row would label the cells to its left, which belong to the
|
||||
// previous month.
|
||||
let columns = window.get_library_columns().max(1) as usize;
|
||||
let mut previous_month: Option<(i64, i64)> = None;
|
||||
let headings: Vec<String> = cells
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
let Some(t) = c.captured_at else {
|
||||
return String::new();
|
||||
};
|
||||
let headings = period_headings(
|
||||
cells.iter().map(|c| {
|
||||
c.captured_at.map(|t| {
|
||||
let (y, m, _, _) = civil_from_unix(t);
|
||||
let is_new = previous_month != Some((y, m));
|
||||
previous_month = Some((y, m));
|
||||
|
||||
// A heading is drawn above its row, so it can only sit on a cell
|
||||
// that begins one — a heading stranded mid-row would appear to
|
||||
// label the cells to its left, which belong to the month before.
|
||||
//
|
||||
// The first cell of the window always carries one, whichever
|
||||
// column it lands in: a scrolled window would otherwise show no
|
||||
// date at all until the next month began.
|
||||
let begins_row = (i + offset) % columns == 0;
|
||||
if i == 0 || (is_new && begins_row) {
|
||||
format!("{} {y}", month_name(m))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
(y, m)
|
||||
})
|
||||
.collect();
|
||||
}),
|
||||
offset,
|
||||
columns,
|
||||
);
|
||||
|
||||
// What the outgoing model is still holding — see [`hold_thumbnails`].
|
||||
let held = {
|
||||
@@ -7151,6 +7130,46 @@ fn stop(slot: &RefCell<Option<slint::Timer>>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The heading each cell of a window carries: a month name on some, nothing
|
||||
/// on most.
|
||||
///
|
||||
/// A heading is drawn above its row, so it can only sit on a cell that
|
||||
/// begins one — stranded mid-row it would appear to label the cells to its
|
||||
/// left, which belong to the month before. So a row carries a heading when
|
||||
/// its first cell's month is not the one last announced. A month that begins
|
||||
/// mid-row is therefore announced at the *next* row it opens, which is one
|
||||
/// row late and still right about every cell under it. It used to be
|
||||
/// announced only if it happened to begin a row, which at seven columns
|
||||
/// meant most months were never named at all and the one heading on screen
|
||||
/// was wrong about everything below its first row.
|
||||
///
|
||||
/// The first cell of the window always carries one, whichever column it
|
||||
/// lands in: a scrolled window would otherwise show no date at all until
|
||||
/// the next month began.
|
||||
fn period_headings(
|
||||
months: impl Iterator<Item = Option<(i64, i64)>>,
|
||||
offset: usize,
|
||||
columns: usize,
|
||||
) -> Vec<String> {
|
||||
let columns = columns.max(1);
|
||||
let mut announced: Option<(i64, i64)> = None;
|
||||
months
|
||||
.enumerate()
|
||||
.map(|(i, month)| {
|
||||
let Some((y, m)) = month else {
|
||||
return String::new();
|
||||
};
|
||||
let begins_row = (i + offset).is_multiple_of(columns);
|
||||
if i == 0 || (begins_row && announced != Some((y, m))) {
|
||||
announced = Some((y, m));
|
||||
format!("{} {y}", month_name(m))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The line in the library header that says what is being catalogued.
|
||||
///
|
||||
/// For an account with a user, the user and the chosen subtree, or the
|
||||
@@ -7180,6 +7199,45 @@ fn library_root_label(account: &Account) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_month_that_begins_mid_row_is_still_announced() {
|
||||
// Four columns; August starts in the second column of the second
|
||||
// row and October in the third of the third. Neither begins a row,
|
||||
// and both must still be named — on the next row each opens.
|
||||
let mar = Some((2024, 3));
|
||||
let aug = Some((2024, 8));
|
||||
let oct = Some((2025, 10));
|
||||
let months = [
|
||||
mar, mar, mar, mar, //
|
||||
mar, aug, aug, aug, //
|
||||
aug, aug, oct, oct, //
|
||||
oct,
|
||||
];
|
||||
let h = period_headings(months.into_iter(), 0, 4);
|
||||
assert_eq!(h[0], "March 2024");
|
||||
assert_eq!(h[4], "", "still March at the top of row two");
|
||||
assert_eq!(h[5], "", "never mid-row");
|
||||
assert_eq!(h[8], "August 2024");
|
||||
assert_eq!(h[12], "October 2025");
|
||||
assert_eq!(h.iter().filter(|s| !s.is_empty()).count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scrolled_window_names_its_first_cell_wherever_it_lands() {
|
||||
let jan = Some((2026, 1));
|
||||
let feb = Some((2026, 2));
|
||||
// Offset 2 into a 4-column grid: the window's first cell is in the
|
||||
// third column, and the row after it begins a new month.
|
||||
let h = period_headings([jan, jan, feb, feb, feb].into_iter(), 2, 4);
|
||||
assert_eq!(h[0], "January 2026");
|
||||
assert_eq!(h[2], "February 2026", "cell 2 begins a row at offset 2");
|
||||
assert_eq!(h[3], "");
|
||||
// Undated cells carry nothing and announce nothing.
|
||||
let h = period_headings([None, jan, None, None, jan].into_iter(), 0, 4);
|
||||
assert_eq!(h[0], "");
|
||||
assert_eq!(h[4], "January 2026");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_header_names_a_folder_library_by_its_folder() {
|
||||
let mut folder = Account::new("folder", "/var/tmp/dr-demo/library");
|
||||
|
||||
Reference in New Issue
Block a user