refactor: isolate rendering
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
use figlet_rs::Toilet;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders, Clear, Paragraph},
|
||||
};
|
||||
|
||||
use crate::app::{Termato, TimerMode};
|
||||
use crate::audio::AudioVisualizer;
|
||||
|
||||
// use unicode blocks for rendering
|
||||
// Empty -> " "
|
||||
// 1/8th height -> "▂"
|
||||
// 2/8th height -> "▃"
|
||||
// 3/8th height -> "▄"
|
||||
// 4/8th height -> "▅"
|
||||
// 5/8th height -> "▆"
|
||||
// 6/8th height -> "▇"
|
||||
// Full Height -> "█"
|
||||
pub fn render_visualizer(f: &mut Frame, area: Rect, bar_values: &[u64]) {
|
||||
const BLOCKS: [&str; 8] = ["▂", "▃", "▄", "▅", "▆", "▇", "█", "█"];
|
||||
|
||||
let max_bars = area.width as usize;
|
||||
let line: String = bar_values
|
||||
.iter()
|
||||
.take(max_bars)
|
||||
.map(|&val| {
|
||||
if val == 0 {
|
||||
" "
|
||||
} else {
|
||||
let idx = ((val as f32 / 100.0) * (BLOCKS.len() - 1) as f32)
|
||||
.clamp(0.0, (BLOCKS.len() - 1) as f32) as usize;
|
||||
|
||||
BLOCKS[idx]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let viz_paragraph = Paragraph::new(line)
|
||||
.style(Style::default().fg(Color::Rgb(200, 184, 224)))
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
f.render_widget(viz_paragraph, area);
|
||||
}
|
||||
|
||||
pub fn render_app(
|
||||
app: &Termato,
|
||||
f: &mut Frame,
|
||||
font: &Toilet,
|
||||
visualizer: Option<&AudioVisualizer>,
|
||||
) {
|
||||
let size = f.area();
|
||||
|
||||
let minutes = app.time_remaining_in_sec / 60;
|
||||
let seconds = app.time_remaining_in_sec % 60;
|
||||
let time_str = format!("{:02}:{:02}", minutes, seconds);
|
||||
|
||||
let time_color = if !app.is_running {
|
||||
Color::Rgb(212, 200, 122)
|
||||
} else {
|
||||
match app.mode {
|
||||
TimerMode::Work => Color::Rgb(212, 115, 115),
|
||||
TimerMode::Break => Color::Rgb(126, 200, 192),
|
||||
}
|
||||
};
|
||||
|
||||
let raw_time_text = if let Some(fig) = font.convert(&time_str) {
|
||||
fig.to_string()
|
||||
} else {
|
||||
time_str
|
||||
};
|
||||
|
||||
let trimmed_time_text = raw_time_text.trim_matches('\n');
|
||||
let text_lines: Vec<&str> = trimmed_time_text.lines().collect();
|
||||
let text_height = text_lines.len() as u16;
|
||||
|
||||
let viz_height = if visualizer.is_some() { 1 } else { 0 };
|
||||
let viz_width = 64;
|
||||
|
||||
let content_height = text_height + viz_height;
|
||||
|
||||
let outer_vertical = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(content_height),
|
||||
Constraint::Min(0),
|
||||
])
|
||||
.split(size);
|
||||
|
||||
let inner_vertical = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(text_height),
|
||||
Constraint::Length(viz_height),
|
||||
])
|
||||
.split(outer_vertical[1]);
|
||||
|
||||
let viz_horizontal = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(viz_width.min(size.width)),
|
||||
Constraint::Min(0),
|
||||
])
|
||||
.split(inner_vertical[1]);
|
||||
|
||||
let timer_paragraph = Paragraph::new(trimmed_time_text)
|
||||
.style(Style::default().fg(time_color))
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
f.render_widget(timer_paragraph, inner_vertical[0]);
|
||||
|
||||
if let Some(viz) = visualizer
|
||||
&& let Ok(bar_data) = viz.bar_data.lock()
|
||||
{
|
||||
render_visualizer(f, viz_horizontal[1], &bar_data);
|
||||
}
|
||||
|
||||
if app.show_help {
|
||||
let popup_width = 44;
|
||||
let popup_height = 7;
|
||||
|
||||
if size.width >= popup_width && size.height >= popup_height {
|
||||
let popup_vertical = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length((size.height.saturating_sub(popup_height)) / 2),
|
||||
Constraint::Length(popup_height),
|
||||
Constraint::Min(1),
|
||||
])
|
||||
.split(size);
|
||||
|
||||
let popup_horizontal = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Length((size.width.saturating_sub(popup_width)) / 2),
|
||||
Constraint::Length(popup_width),
|
||||
Constraint::Min(1),
|
||||
])
|
||||
.split(popup_vertical[1]);
|
||||
|
||||
let popup_area = popup_horizontal[1];
|
||||
|
||||
let help_block = Block::default()
|
||||
.title(" Controls ")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Rgb(58, 62, 70)));
|
||||
|
||||
let help_content = Paragraph::new(
|
||||
"[Space] Pause / Resume\n\
|
||||
[S] Switch Mode\n\
|
||||
[R] Reset Timer\n\
|
||||
[?] Toggle Help\n\
|
||||
[Q] Quit",
|
||||
)
|
||||
.block(help_block)
|
||||
.style(Style::default().fg(Color::Rgb(224, 228, 232)))
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
f.render_widget(Clear, popup_area);
|
||||
f.render_widget(help_content, popup_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user