⭐
Log in to mark this repository.
calendar: scroll the upcoming list into other years
1457011 · Gabriel Morell · 2026-10-19 15:09
✓
Rust CI and Docker Build / Create Release (push)
✓
Rust CI and Docker Build / Release (7z a, .exe, ppl-x86_64-windows.zip, windows-latest, windows-latest, 1.92) (push)
✓
Rust CI and Docker Build / Release (tar -czf, , ppl-aarch64-linux-gnu.tar.gz, ubuntu-24.04-arm, ubuntu-24.04-arm, 1.92) (push)
✓
Rust CI and Docker Build / Release (tar -czf, , ppl-aarch64-macos.tar.gz, macos-latest, macos-latest, 1.92) (push)
✓
Rust CI and Docker Build / Release (tar -czf, , ppl-x86_64-linux-gnu.tar.gz, ubuntu-latest, arc-runner-set, 1.92) (push)
✗
Rust CI and Docker Build / build (aarch64, ubuntu-24.04-arm) (push)
✓
Rust CI and Docker Build / build (x86_64, arc-runner-set) (push)
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