Calendar: independent week scrolling and weekend colors

- calendar_scroll replaced by per-week calendar_scrolls[4] + active_week
- Tab cycles through weeks within Calendar, Left/Right switch week
- Up/Down scroll only the active week independently
- Sat/Sun rendered in Magenta, weekdays in Cyan, today in Yellow
This commit is contained in:
Ruben Rosario
2026-06-21 18:57:07 +01:00
parent 00fec516ac
commit 10a8d1d75e
4 changed files with 62 additions and 35 deletions
+48 -18
View File
@@ -39,7 +39,8 @@ pub struct App {
pub task_list_scroll: u16, pub task_list_scroll: u16,
pub detail_scroll: u16, pub detail_scroll: u16,
pub notes_scroll: u16, pub notes_scroll: u16,
pub calendar_scroll: u16, pub calendar_scrolls: [u16; 4],
pub calendar_active_week: usize,
pub db: Arc<Db>, pub db: Arc<Db>,
#[allow(dead_code)] #[allow(dead_code)]
pub api_client: Arc<ApiClient>, pub api_client: Arc<ApiClient>,
@@ -110,7 +111,8 @@ impl App {
task_list_scroll: 0, task_list_scroll: 0,
detail_scroll: 0, detail_scroll: 0,
notes_scroll: 0, notes_scroll: 0,
calendar_scroll: 0, calendar_scrolls: [0; 4],
calendar_active_week: 0,
db, db,
api_client, api_client,
needs_auth: !has_token, needs_auth: !has_token,
@@ -312,12 +314,24 @@ impl App {
match key.code { match key.code {
KeyCode::Tab => { KeyCode::Tab => {
self.focus = match self.focus { match self.focus {
Focus::Tabs => Focus::TaskList, Focus::Calendar => {
Focus::TaskList => Focus::Detail, if self.calendar_active_week < 3 {
Focus::Detail => Focus::Calendar, self.calendar_active_week += 1;
Focus::Calendar => Focus::Tabs, } else {
}; self.calendar_active_week = 0;
self.focus = Focus::Tabs;
}
}
_ => {
self.focus = match self.focus {
Focus::Tabs => Focus::TaskList,
Focus::TaskList => Focus::Detail,
Focus::Detail => Focus::Calendar,
_ => Focus::Tabs,
};
}
}
} }
KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => { KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
if self.focus == Focus::TaskList && !self.tasks.is_empty() { if self.focus == Focus::TaskList && !self.tasks.is_empty() {
@@ -340,7 +354,7 @@ impl App {
self.detail_scroll = self.detail_scroll.saturating_sub(1); self.detail_scroll = self.detail_scroll.saturating_sub(1);
} }
Focus::Calendar => { Focus::Calendar => {
self.calendar_scroll = self.calendar_scroll.saturating_sub(1); self.calendar_scrolls[self.calendar_active_week] = self.calendar_scrolls[self.calendar_active_week].saturating_sub(1);
} }
_ => {} _ => {}
}, },
@@ -355,24 +369,40 @@ impl App {
self.detail_scroll += 1; self.detail_scroll += 1;
} }
Focus::Calendar => { Focus::Calendar => {
self.calendar_scroll += 1; self.calendar_scrolls[self.calendar_active_week] = self.calendar_scrolls[self.calendar_active_week].saturating_add(1);
} }
_ => {} _ => {}
}, },
KeyCode::Right => { KeyCode::Right => {
if self.focus == Focus::Tabs && !self.lists.is_empty() { match self.focus {
if self.selected_list + 1 < self.lists.len() { Focus::Tabs => {
self.selected_list += 1; if !self.lists.is_empty() && self.selected_list + 1 < self.lists.len() {
self.load_tasks(); self.selected_list += 1;
self.load_tasks();
}
} }
Focus::Calendar => {
if self.calendar_active_week < 3 {
self.calendar_active_week += 1;
}
}
_ => {}
} }
} }
KeyCode::Left => { KeyCode::Left => {
if self.focus == Focus::Tabs && !self.lists.is_empty() { match self.focus {
if self.selected_list > 0 { Focus::Tabs => {
self.selected_list -= 1; if !self.lists.is_empty() && self.selected_list > 0 {
self.load_tasks(); self.selected_list -= 1;
self.load_tasks();
}
} }
Focus::Calendar => {
if self.calendar_active_week > 0 {
self.calendar_active_week -= 1;
}
}
_ => {}
} }
} }
KeyCode::Char('n') | KeyCode::Char('N') => { KeyCode::Char('n') | KeyCode::Char('N') => {
+2 -1
View File
@@ -150,7 +150,8 @@ fn main() -> io::Result<()> {
task_list_scroll: app.task_list_scroll, task_list_scroll: app.task_list_scroll,
detail_scroll: app.detail_scroll, detail_scroll: app.detail_scroll,
notes_scroll: app.notes_scroll, notes_scroll: app.notes_scroll,
calendar_scroll: app.calendar_scroll, calendar_scrolls: &app.calendar_scrolls,
calendar_active_week: app.calendar_active_week,
auth_error: app.auth_error.as_deref(), auth_error: app.auth_error.as_deref(),
sync_stats: &app.sync_stats, sync_stats: &app.sync_stats,
}; };
+8 -14
View File
@@ -602,7 +602,8 @@ pub fn render_calendar_panel(
area: Rect, area: Rect,
events: &[CalendarEvent], events: &[CalendarEvent],
focused: bool, focused: bool,
_scroll: u16, scrolls: &[u16; 4],
active_week: usize,
) { ) {
if area.width < 20 || area.height < 3 { if area.width < 20 || area.height < 3 {
return; return;
@@ -629,7 +630,7 @@ pub fn render_calendar_panel(
let week_title = format!(" W/C {} ", week_start.format("%d/%m")); let week_title = format!(" W/C {} ", week_start.format("%d/%m"));
let col_area = cols[week_idx]; let col_area = cols[week_idx];
let border = if focused { FOCUS_COLOR } else { Color::DarkGray }; let border = if focused && week_idx == active_week { FOCUS_COLOR } else { Color::DarkGray };
let block = Block::default() let block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(Style::default().fg(border)) .border_style(Style::default().fg(border))
@@ -651,9 +652,6 @@ pub fn render_calendar_panel(
continue; continue;
} }
let inner = block.inner(col_area);
let inner_h = inner.height as usize;
let mut lines: Vec<Line> = Vec::new(); let mut lines: Vec<Line> = Vec::new();
for day_offset in 0..7 { for day_offset in 0..7 {
@@ -661,6 +659,8 @@ pub fn render_calendar_panel(
let day_style = if day == today { let day_style = if day == today {
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD) Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
} else if matches!(day.weekday(), chrono::Weekday::Sat | chrono::Weekday::Sun) {
Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD)
} else { } else {
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD) Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
}; };
@@ -679,10 +679,6 @@ pub fn render_calendar_panel(
); );
lines.push(Line::from(Span::styled(day_label, day_style))); lines.push(Line::from(Span::styled(day_label, day_style)));
if lines.len() >= inner_h {
break;
}
for event in events.iter().filter(|e| e.start.map_or(false, |s| s.date() == day)) { for event in events.iter().filter(|e| e.start.map_or(false, |s| s.date() == day)) {
let time_str = event.start.map(|s| s.format("%H:%M").to_string()).unwrap_or_default(); let time_str = event.start.map(|s| s.format("%H:%M").to_string()).unwrap_or_default();
let line_text = format!(" {} {}", time_str, event.summary); let line_text = format!(" {} {}", time_str, event.summary);
@@ -690,14 +686,12 @@ pub fn render_calendar_panel(
line_text, line_text,
Style::default().fg(DETAIL_COLOR), Style::default().fg(DETAIL_COLOR),
))); )));
if lines.len() >= inner_h {
break;
}
} }
} }
let paragraph = Paragraph::new(Text::from(lines)).block(block); let paragraph = Paragraph::new(Text::from(lines))
.block(block)
.scroll((scrolls[week_idx], 0));
frame.render_widget(paragraph, col_area); frame.render_widget(paragraph, col_area);
} }
} }
+4 -2
View File
@@ -48,7 +48,8 @@ pub struct AppView<'a> {
pub detail_scroll: u16, pub detail_scroll: u16,
pub notes_scroll: u16, pub notes_scroll: u16,
pub calendar_events: &'a [CalendarEvent], pub calendar_events: &'a [CalendarEvent],
pub calendar_scroll: u16, pub calendar_scrolls: &'a [u16; 4],
pub calendar_active_week: usize,
pub auth_error: Option<&'a str>, pub auth_error: Option<&'a str>,
pub sync_stats: &'a SyncStats, pub sync_stats: &'a SyncStats,
} }
@@ -104,7 +105,8 @@ pub fn draw(frame: &mut Frame, view: AppView) {
calendar_area, calendar_area,
view.calendar_events, view.calendar_events,
is_calendar_focused, is_calendar_focused,
view.calendar_scroll, view.calendar_scrolls,
view.calendar_active_week,
); );
render_status_bar(frame, status_area, view.network_status, view.sync_stats); render_status_bar(frame, status_area, view.network_status, view.sync_stats);