tui: show and hide kinds of dates on the calendar tab

0f89366 · Gabriel Morell · 2026-10-19 16:35

3 files +87 -26
Message
{commit_body(@commit)}

Files changed

modified README.md
+4 −1
@@ -77,7 +77,10 @@ While editing a person, `Delete` (or backspace) twice removes the selected row.
77 77
78 78 Upcoming anniversaries and holidays for the year; `↑`/`↓` scroll the upcoming list, which opens on today,
79 79 and the selected day is highlighted in white in the month grid. Scrolling past the end of the year
80 −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
80 +carries on into the next (and above the start, into the last); `PgUp`/`PgDn` jump a year.
81 +Like the ppl list's toggles, `n` (countries), `r` (religious), `m` (moon and fiscal), `s` (subscribed)
82 +and `p` (people's dates) show or hide those dates on the calendar tab; hidden ones are struck
83 +through in the footer. Press `i` on a holiday for a short
81 84 description and a Wikipedia link (`o` opens it in your browser).
82 85
83 86 Press `c` to switch built-in calendars on and off. Their holidays show in the upcoming list and
modified src/do_tui.rs
+83 −25
@@ -7,7 +7,7 @@ use crate::entities::ppl::Model;
7 7 use crate::entities::{
8 8 calendar, contact, ppl, relation, sig_date, tier, tier_defaults, trait_defaults, traits,
9 9 };
10 −use crate::holidays::{holidays_in, Holiday};
10 +use crate::holidays::{group_of, holidays_in, Holiday};
11 11 use crate::tui_add::{render_person_menu, AddContext, AddForm, AddKind, FormAction};
12 12 use crate::tui_calendars::{
13 13 holiday_color, open_url, render_holiday_info, CalendarMenu, MenuAction,
@@ -82,6 +82,8 @@ struct Tui {
82 82 holiday_info: Option<Holiday>,
83 83 /// The year the calendar tab shows; scrolling past either end moves it
84 84 cal_year: i32,
85 + /// Holiday groups (by key) and "people" hidden on the calendar tab
86 + cal_hidden: Vec<&'static str>,
85 87 }
86 88
87 89 #[derive(Debug, PartialEq, Sequence, Clone)]
@@ -213,6 +215,7 @@ impl Default for Tui {
213 215 upcoming_holidays: vec![],
214 216 holiday_info: None,
215 217 cal_year: Local::now().year(),
218 + cal_hidden: vec![],
216 219 }
217 220 }
218 221 }
@@ -428,6 +431,21 @@ pub async fn run_tui(
428 431 KeyCode::Char(_) if app.current_tab == Tabs::Ppl && app.ppl_field_editing => {
429 432 default_editing_handler(&mut app, &key_event);
430 433 }
434 + // Show or hide a kind of date on the calendar tab, like the ppl tab's toggles
435 + KeyCode::Char(c)
436 + if app.current_tab == Tabs::Calendar && cal_toggle(c).is_some() =>
437 + {
438 + if let Some(what) = cal_toggle(c) {
439 + match app.cal_hidden.iter().position(|h| *h == what) {
440 + Some(i) => {
441 + app.cal_hidden.remove(i);
442 + }
443 + None => app.cal_hidden.push(what),
444 + }
445 + // The list changed under the cursor; start again from today
446 + app.sigdate_cal_state = ListState::default();
447 + }
448 + }
431 449 KeyCode::Char('e') => {
432 450 if app.current_tab == Tabs::Ppl {
433 451 match app.ppl_field_editing {
@@ -602,6 +620,22 @@ pub async fn run_tui(
602 620 }
603 621 }
604 622
623 +/// What a key shows or hides on the calendar tab: a holiday group's key, or "people"
624 +const CAL_TOGGLES: [(char, &str, &str); 5] = [
625 + ('n', "countries", "countries"),
626 + ('r', "religious", "religious"),
627 + ('m', "other", "moon/fiscal"),
628 + ('s', "subscribed", "subscribed"),
629 + ('p', "people", "people"),
630 +];
631 +
632 +fn cal_toggle(key: char) -> Option<&'static str> {
633 + CAL_TOGGLES
634 + .iter()
635 + .find(|(k, _, _)| *k == key)
636 + .map(|(_, what, _)| *what)
637 +}
638 +
605 639 /// The tier row the reminder keys act on: a tier default on the tiers tab,
606 640 /// or the selected tier of a person being edited on the ppl tab
607 641 struct ReminderTarget {
@@ -1528,7 +1562,13 @@ fn render(f: &mut Frame, app: &mut Tui) {
1528 1562 );
1529 1563
1530 1564 // Holidays first, so a person's date on the same day wins the colour
1531 − for h in &app.holidays {
1565 + let shown = app
1566 + .holidays
1567 + .iter()
1568 + .filter(|h| !app.cal_hidden.contains(&group_of(h.calendar).key))
1569 + .collect::<Vec<_>>();
1570 + let show_people = !app.cal_hidden.contains(&"people");
1571 + for h in &shown {
1532 1572 let Ok(day) = Month::try_from(h.date.month() as u8).and_then(|month| {
1533 1573 Date::from_calendar_date(h.date.year(), month, h.date.day() as u8)
1534 1574 }) else {
@@ -1543,7 +1583,10 @@ fn render(f: &mut Frame, app: &mut Tui) {
1543 1583 );
1544 1584 }
1545 1585
1546 − for u in as_upcoming.iter().filter(|d| d.date.do_remind) {
1586 + for u in as_upcoming
1587 + .iter()
1588 + .filter(|d| d.date.do_remind && show_people)
1589 + {
1547 1590 let Ok(day) = Month::try_from(u.date.date.month() as u8).and_then(|month| {
1548 1591 Date::from_calendar_date(start.year(), month, u.date.date.day() as u8)
1549 1592 }) else {
@@ -1561,12 +1604,13 @@ fn render(f: &mut Frame, app: &mut Tui) {
1561 1604 // People's dates and holidays together, in date order
1562 1605 let mut upcoming = as_upcoming
1563 1606 .iter()
1607 + .filter(|_| show_people)
1564 1608 .map(|u| (u.date.date, None, ListItem::from(u)))
1565 1609 .collect::<Vec<_>>();
1566 1610 upcoming.extend(
1567 − app.holidays
1611 + shown
1568 1612 .iter()
1569 − .map(|h| (h.date, Some(h.clone()), holiday_item(h))),
1613 + .map(|h| (h.date, Some((*h).clone()), holiday_item(h))),
1570 1614 );
1571 1615 upcoming.sort_by_key(|(d, _, _)| *d);
1572 1616 // Open on the first date from today on, with a couple of past ones above it
@@ -1655,32 +1699,46 @@ fn render(f: &mut Frame, app: &mut Tui) {
1655 1699 // .constraints([Constraint::Percentage(25), Constraint::Percentage(75)])
1656 1700 // .split(chunks[1]);
1657 1701
1658 − let msgw = vec![
1702 + let key = |k: &str| {
1659 1703 Span::styled(
1660 − "Tab",
1704 + k.to_string(),
1661 1705 Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
1662 − ),
1663 − Span::raw(" to switch views. "),
1664 − Span::styled("E", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)),
1665 − Span::raw(" to edit the enabled tiers. "),
1666 − Span::styled("C", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)),
1706 + )
1707 + };
1708 + let msgw = vec![
1709 + key("Tab"),
1710 + Span::raw(" views · "),
1711 + key("c"),
1667 1712 Span::raw(format!(
1668 − " to choose built-in holiday calendars ({} on). ",
1713 + " calendars ({} on) · ",
1669 1714 app.calendars_enabled.len()
1670 1715 )),
1671 − Span::styled(
1672 − "↑/↓",
1673 − Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
1674 − ),
1675 − Span::raw(" to scroll upcoming, "),
1676 − Span::styled(
1677 − "PgUp/PgDn",
1678 − Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
1679 − ),
1680 − Span::raw(" for another year. "),
1681 − Span::styled("i", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)),
1682 − Span::raw(" about a holiday. "),
1716 + key("↑/↓"),
1717 + Span::raw(" scroll · "),
1718 + key("PgUp/PgDn"),
1719 + Span::raw(" year · "),
1720 + key("i"),
1721 + Span::raw(" about · show:"),
1683 1722 ];
1723 + let mut msgw = msgw;
1724 + // Like the ppl tab: a hidden kind is struck through
1725 + for (key, what, label) in CAL_TOGGLES {
1726 + let hidden = app.cal_hidden.contains(&what);
1727 + let style = match hidden {
1728 + true => Style::default()
1729 + .fg(SLATE.c600)
1730 + .add_modifier(Modifier::CROSSED_OUT),
1731 + false => Style::default(),
1732 + };
1733 + msgw.push(Span::raw(" "));
1734 + msgw.push(Span::styled(
1735 + key.to_string(),
1736 + style
1737 + .add_modifier(Modifier::BOLD)
1738 + .fg(if hidden { SLATE.c600 } else { WHITE }),
1739 + ));
1740 + msgw.push(Span::styled(format!(" {}", label), style));
1741 + }
1684 1742 let textw = Text::from(Line::from(msgw)).style(Style::default());
1685 1743 let welcome = Paragraph::new(textw);
1686 1744 f.render_widget(welcome, chunks[2]);

Parents: 2c2327e