Skip to main content

void/ui/
zen.rs

1use super::*;
2
3pub(crate) fn draw_zen_dashboard(f: &mut Frame, app: &App, area: Rect) {
4    let theme = &app.theme;
5    let icons = app.icons;
6    let t = &app.timer;
7    let on_break = is_break_mode(t.mode);
8    let mc = mode_color(theme, t.mode);
9
10    let chunks = Layout::default()
11        .constraints([
12            Constraint::Min(1),
13            Constraint::Length(1),
14            Constraint::Length(if on_break { 3 } else { 0 }),
15            Constraint::Length(2),
16        ])
17        .split(area);
18
19    let cycle = t.config.long_break_every.max(1);
20    let style = theme.scene_style(mc);
21    let options = ZenSceneOptions {
22        task_progress: app.active_task_progress(),
23        sessions_done: t.completed_focus_sessions % cycle,
24        sessions_total: cycle,
25        pending_tasks: app.pending_task_count(),
26        active_task_index: app.active_task_pending_index(),
27        layout: crate::canvas_timer::SceneLayout::Zen,
28    };
29    draw_zen_canvas(f, chunks[0], t, &style, &options);
30
31    let (main_time, tenths, _) = format_time_stack(t);
32    let session_label = t.cycle_label();
33    let mut overlay_lines = vec![
34        Line::from(vec![
35            Span::styled(
36                main_time,
37                Style::default().fg(theme.text).add_modifier(Modifier::BOLD),
38            ),
39            Span::styled(tenths, Style::default().fg(theme.dim)),
40        ]),
41        Line::from(Span::styled(session_label, Style::default().fg(theme.dim))),
42    ];
43    if let Some(id) = app.active_task {
44        if let Some(task) = app.data.tasks.iter().find(|t| t.id == id) {
45            overlay_lines.push(Line::from(Span::styled(
46                format!("{} {}", icons.task_active, truncate(&task.title, 36)),
47                Style::default()
48                    .fg(theme.accent)
49                    .add_modifier(Modifier::BOLD),
50            )));
51        }
52    }
53    let time_area = Rect {
54        x: chunks[0].x,
55        y: chunks[0].y + chunks[0].height / 2 - 2,
56        width: chunks[0].width,
57        height: overlay_lines.len() as u16,
58    };
59    f.render_widget(
60        Paragraph::new(overlay_lines).alignment(Alignment::Center),
61        time_area,
62    );
63
64    let task_line = if let Some(id) = app.active_task {
65        app.data.tasks.iter().find(|t| t.id == id).map(|task| {
66            Line::from(vec![
67                Span::styled(
68                    format!("{} ", icons.task_active),
69                    Style::default().fg(theme.accent),
70                ),
71                Span::styled(
72                    truncate(&task.title, 48),
73                    Style::default().fg(theme.text).add_modifier(Modifier::BOLD),
74                ),
75                Span::styled(
76                    format!(
77                        "  {} {}",
78                        task_status_icon(icons, task.status),
79                        task.status.short_label()
80                    ),
81                    Style::default().fg(task_status_color(theme, task.status)),
82                ),
83            ])
84        })
85    } else {
86        None
87    };
88    f.render_widget(
89        Paragraph::new(task_line.unwrap_or_else(|| {
90            Line::from(Span::styled(
91                if app.queue_empty() {
92                    format!("{} All tasks done — free focus", icons.check)
93                } else {
94                    format!("{} No active task — [f] on dashboard", icons.dot)
95                },
96                Style::default().fg(theme.dim),
97            ))
98        }))
99        .alignment(Alignment::Center),
100        chunks[1],
101    );
102
103    if on_break {
104        draw_break_tip(f, chunks[2], t, mc, theme.text, theme.dim, icons.heart);
105    }
106
107    chrome::draw_footer(f, app, chunks[3]);
108}