feat: show fullscreen timer view

This commit is contained in:
2026-07-05 02:31:58 +02:00
parent f4c54837de
commit ce35617bbe
+132 -4
View File
@@ -958,6 +958,10 @@ impl App {
frame.render_widget(Paragraph::new("terminal too small"), area); frame.render_widget(Paragraph::new("terminal too small"), area);
return; return;
} }
if self.timer.is_some() {
self.draw_running(frame, area);
return;
}
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@@ -984,6 +988,90 @@ impl App {
); );
} }
fn draw_running(&self, frame: &mut Frame, area: Rect) {
let (_, remaining, progress) = self.timer_snapshot();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(2),
Constraint::Length(7),
Constraint::Length(2),
Constraint::Min(4),
Constraint::Length(1),
])
.split(area);
frame.render_widget(
Paragraph::new("gosleep-timer").style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
chunks[0],
);
let time_lines = ascii_time(&format_compact_duration(remaining))
.into_iter()
.map(|line| Line::from(Span::styled(line, Style::default().fg(Color::Cyan))))
.collect::<Vec<_>>();
frame.render_widget(Paragraph::new(time_lines), chunks[1]);
frame.render_widget(
Gauge::default()
.gauge_style(Style::default().fg(Color::Magenta))
.label(format_gauge_label(progress))
.ratio(progress),
chunks[2],
);
let commands = build_action_commands(&self.config);
let lines = if commands.is_empty() {
vec![Line::from("no post-timer actions enabled")]
} else {
commands
.iter()
.flat_map(|command| {
let style = if command.is_dangerous() {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::Green)
};
wrap_command(
&command.display(),
chunks[3].width.saturating_sub(4) as usize,
)
.into_iter()
.map(move |line| Line::from(Span::styled(line, style)))
})
.collect::<Vec<_>>()
};
frame.render_widget(
Paragraph::new(lines)
.block(
Block::default()
.title(" commands after countdown ")
.borders(Borders::ALL)
.border_style(Color::Blue),
)
.wrap(Wrap { trim: false }),
chunks[3],
);
let footer = if self
.timer
.as_ref()
.is_some_and(|timer| timer.pause.load(Ordering::Relaxed))
{
"paused | p resume | x stop | q quit"
} else {
"running | p pause | x stop | q quit"
};
frame.render_widget(
Paragraph::new(footer).style(Style::default().fg(Color::DarkGray)),
chunks[4],
);
}
fn draw_header(&self, frame: &mut Frame, area: Rect) { fn draw_header(&self, frame: &mut Frame, area: Rect) {
let (_, remaining, progress) = self.timer_snapshot(); let (_, remaining, progress) = self.timer_snapshot();
let header = Layout::default() let header = Layout::default()
@@ -1090,10 +1178,19 @@ impl App {
} }
fn draw_preview(&self, frame: &mut Frame, area: Rect) { fn draw_preview(&self, frame: &mut Frame, area: Rect) {
let lines = preview_commands(&self.config) let commands = build_action_commands(&self.config);
.into_iter() let lines = commands
.flat_map(|command| wrap_command(&command, area.width.saturating_sub(4) as usize)) .iter()
.map(|line| Line::from(Span::styled(line, Style::default().fg(Color::Green)))) .flat_map(|command| {
let style = if command.is_dangerous() {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::Green)
};
wrap_command(&command.display(), area.width.saturating_sub(4) as usize)
.into_iter()
.map(move |line| Line::from(Span::styled(line, style)))
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let lines = if lines.is_empty() { let lines = if lines.is_empty() {
vec![Line::from("no post-timer actions enabled")] vec![Line::from("no post-timer actions enabled")]
@@ -1573,6 +1670,37 @@ fn format_gauge_label(progress: f64) -> String {
format!("{:>3}%", (progress * 100.0).round() as u16) format!("{:>3}%", (progress * 100.0).round() as u16)
} }
fn ascii_time(value: &str) -> Vec<String> {
let mut rows = vec![String::new(); 5];
for ch in value.chars() {
let glyph = ascii_glyph(ch);
for (row, part) in rows.iter_mut().zip(glyph) {
if !row.is_empty() {
row.push(' ');
}
row.push_str(part);
}
}
rows
}
fn ascii_glyph(ch: char) -> [&'static str; 5] {
match ch {
'0' => [" ### ", "# #", "# #", "# #", " ### "],
'1' => [" # ", " ## ", " # ", " # ", " ### "],
'2' => [" ### ", "# #", " # ", " # ", "#####"],
'3' => ["#### ", " #", " ### ", " #", "#### "],
'4' => ["# #", "# #", "#####", " #", " #"],
'5' => ["#####", "# ", "#### ", " #", "#### "],
'6' => [" ### ", "# ", "#### ", "# #", " ### "],
'7' => ["#####", " #", " # ", " # ", " # "],
'8' => [" ### ", "# #", " ### ", "# #", " ### "],
'9' => [" ### ", "# #", " ####", " #", " ### "],
':' => [" ", " # ", " ", " # ", " "],
_ => [" ", " ", " ", " ", " "],
}
}
fn wrap_command(command: &str, width: usize) -> Vec<String> { fn wrap_command(command: &str, width: usize) -> Vec<String> {
let width = width.max(8); let width = width.max(8);
let mut lines = Vec::new(); let mut lines = Vec::new();