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:
+89
-31
@@ -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
|
// 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
|
// wall of thumbnails gives no sense of *when* you are looking — the
|
||||||
// sidebar says it, but only if you consult it.
|
// 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 columns = window.get_library_columns().max(1) as usize;
|
||||||
let mut previous_month: Option<(i64, i64)> = None;
|
let headings = period_headings(
|
||||||
let headings: Vec<String> = cells
|
cells.iter().map(|c| {
|
||||||
.iter()
|
c.captured_at.map(|t| {
|
||||||
.enumerate()
|
let (y, m, _, _) = civil_from_unix(t);
|
||||||
.map(|(i, c)| {
|
(y, m)
|
||||||
let Some(t) = c.captured_at else {
|
})
|
||||||
return String::new();
|
}),
|
||||||
};
|
offset,
|
||||||
let (y, m, _, _) = civil_from_unix(t);
|
columns,
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// What the outgoing model is still holding — see [`hold_thumbnails`].
|
// What the outgoing model is still holding — see [`hold_thumbnails`].
|
||||||
let held = {
|
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.
|
/// 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
|
/// 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 {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn the_header_names_a_folder_library_by_its_folder() {
|
fn the_header_names_a_folder_library_by_its_folder() {
|
||||||
let mut folder = Account::new("folder", "/var/tmp/dr-demo/library");
|
let mut folder = Account::new("folder", "/var/tmp/dr-demo/library");
|
||||||
|
|||||||
Reference in New Issue
Block a user