calendar: scroll the upcoming list into other years

1457011 · Gabriel Morell · 2026-10-19 15:09

3 files +41 -8
Message
{commit_body(@commit)}

Files changed

modified README.md
+2 −1
@@ -71,7 +71,8 @@ While editing a person, `Delete` (or backspace) twice removes the selected row.
71 71 #### Calendar View
72 72
73 73 Upcoming anniversaries and holidays for the year; `↑`/`↓` scroll the upcoming list, which opens on today,
74 −and the selected day is highlighted in white in the month grid. Press `i` on a holiday for a short
74 +and the selected day is highlighted in white in the month grid. Scrolling past the end of the year
75 +carries on into the next (and above the start, into the last); `PgUp`/`PgDn` jump a year. Press `i` on a holiday for a short
75 76 description and a Wikipedia link (`o` opens it in your browser).
76 77
77 78 Press `c` to switch built-in calendars on and off. Their holidays show in the upcoming list and
modified src/do_tui.rs
+39 −7
@@ -79,6 +79,8 @@ struct Tui {
79 79 upcoming_holidays: Vec<Option<Holiday>>,
80 80 /// The holiday whose blurb is showing
81 81 holiday_info: Option<Holiday>,
82 + /// The year the calendar tab shows; scrolling past either end moves it
83 + cal_year: i32,
82 84 }
83 85
84 86 #[derive(Debug, PartialEq, Sequence, Clone)]
@@ -136,6 +138,15 @@ impl Tui {
136 138 }
137 139 }
138 140
141 + /// Show another year on the calendar tab, with the cursor at `select`
142 + /// (None picks the first date from today on, like opening the tab)
143 + fn set_cal_year(&mut self, year: i32, select: Option<usize>) {
144 + self.cal_year = year;
145 + self.holidays = holidays_in(year, &self.calendars_enabled);
146 + self.sigdate_cal_state = ListState::default();
147 + self.sigdate_cal_state.select(select);
148 + }
149 +
139 150 fn selected_person(&self) -> Option<&ppl::Model> {
140 151 self.ppl_state
141 152 .selected()
@@ -153,7 +164,7 @@ impl Tui {
153 164 self.sigdate_list = SigDateOps::list(db).await?;
154 165 self.calendars_enabled = CalendarOps::enabled(db).await?;
155 166 self.calendar_rows = CalendarOps::rows(db).await?;
156 − self.holidays = holidays_in(Local::now().year(), &self.calendars_enabled);
167 + self.holidays = holidays_in(self.cal_year, &self.calendars_enabled);
157 168
158 169 Ok(())
159 170 }
@@ -200,6 +211,7 @@ impl Default for Tui {
200 211 calendar_rows: vec![],
201 212 upcoming_holidays: vec![],
202 213 holiday_info: None,
214 + cal_year: Local::now().year(),
203 215 }
204 216 }
205 217 }
@@ -369,7 +381,11 @@ pub async fn run_tui(
369 381
370 382 false => app.ppl_state.scroll_up_by(1),
371 383 },
372 − Tabs::Calendar => app.sigdate_cal_state.scroll_up_by(1),
384 + // Above the first date of the year is the end of the year before
385 + Tabs::Calendar => match app.sigdate_cal_state.selected() {
386 + Some(0) | None => app.set_cal_year(app.cal_year - 1, Some(usize::MAX)),
387 + Some(_) => app.sigdate_cal_state.scroll_up_by(1),
388 + },
373 389 Tabs::TierSettings => app.def_tier_state.scroll_up_by(1),
374 390 Tabs::TraitSettings => app.def_trait_state.scroll_up_by(1),
375 391 },
@@ -381,7 +397,12 @@ pub async fn run_tui(
381 397 },
382 398 false => app.ppl_state.scroll_down_by(1),
383 399 },
384 − Tabs::Calendar => app.sigdate_cal_state.scroll_down_by(1),
400 + Tabs::Calendar => match app.sigdate_cal_state.selected() {
401 + Some(i) if i.saturating_add(1) >= app.upcoming_holidays.len() => {
402 + app.set_cal_year(app.cal_year + 1, Some(0))
403 + }
404 + _ => app.sigdate_cal_state.scroll_down_by(1),
405 + },
385 406 Tabs::TierSettings => app.def_tier_state.scroll_down_by(1),
386 407 Tabs::TraitSettings => app.def_trait_state.scroll_down_by(1),
387 408 },
@@ -392,6 +413,12 @@ pub async fn run_tui(
392 413 false => app.current_tab = next(&app.current_tab).unwrap_or(Tabs::Ppl),
393 414 },
394 415 KeyCode::Esc => break Ok(()),
416 + KeyCode::PageUp if app.current_tab == Tabs::Calendar => {
417 + app.set_cal_year(app.cal_year - 1, None)
418 + }
419 + KeyCode::PageDown if app.current_tab == Tabs::Calendar => {
420 + app.set_cal_year(app.cal_year + 1, None)
421 + }
395 422 // While typing in a field, letters are text, not the section toggles below
396 423 KeyCode::Char(_) if app.current_tab == Tabs::Ppl && app.ppl_field_editing => {
397 424 default_editing_handler(&mut app, &key_event);
@@ -1463,8 +1490,7 @@ fn render(f: &mut Frame, app: &mut Tui) {
1463 1490 .split(chunks[1]);
1464 1491
1465 1492 // Build the year start from chrono; time's now_local() can fail depending on platform and threads
1466 − let Ok(mut start) = Date::from_calendar_date(Local::now().year(), Month::January, 1)
1467 − else {
1493 + let Ok(mut start) = Date::from_calendar_date(app.cal_year, Month::January, 1) else {
1468 1494 return;
1469 1495 };
1470 1496 let as_upcoming = &app
@@ -1539,6 +1565,7 @@ fn render(f: &mut Frame, app: &mut Tui) {
1539 1565 );
1540 1566 upcoming.sort_by_key(|(d, _, _)| *d);
1541 1567 // Open on the first date from today on, with a couple of past ones above it
1568 + // (the first date of a later year, the last of an earlier one)
1542 1569 if app.sigdate_cal_state.selected().is_none() && !upcoming.is_empty() {
1543 1570 let today = Local::now().date_naive();
1544 1571 let idx = upcoming
@@ -1600,7 +1627,7 @@ fn render(f: &mut Frame, app: &mut Tui) {
1600 1627 }
1601 1628
1602 1629 let dates = List::new(upcoming.into_iter().map(|(_, _, item)| item))
1603 − .block(Block::bordered().title("upcoming"))
1630 + .block(Block::bordered().title(format!("upcoming · {}", app.cal_year)))
1604 1631 .style(Style::new().white())
1605 1632 .highlight_style(Style::new().italic().bold())
1606 1633 .highlight_symbol(">>")
@@ -1632,7 +1659,12 @@ fn render(f: &mut Frame, app: &mut Tui) {
1632 1659 "↑/↓",
1633 1660 Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
1634 1661 ),
1635 − Span::raw(" to scroll upcoming. "),
1662 + Span::raw(" to scroll upcoming, "),
1663 + Span::styled(
1664 + "PgUp/PgDn",
1665 + Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
1666 + ),
1667 + Span::raw(" for another year. "),
1636 1668 Span::styled("i", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)),
1637 1669 Span::raw(" about a holiday. "),
1638 1670 ];

Parents: d9460a8