241 lines
9.7 KiB
Rust
241 lines
9.7 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use crate::common::{AppEditMode, AppTime, AppTimeFormat, Content};
|
|
use ratatui::{
|
|
buffer::Buffer,
|
|
layout::{Constraint, Layout, Rect},
|
|
style::{Modifier, Style},
|
|
symbols::{border, scrollbar},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, Widget},
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FooterState {
|
|
show_menu: bool,
|
|
app_time_format: Option<AppTimeFormat>,
|
|
}
|
|
|
|
impl FooterState {
|
|
pub const fn new(show_menu: bool, app_time_format: Option<AppTimeFormat>) -> Self {
|
|
Self {
|
|
show_menu,
|
|
app_time_format,
|
|
}
|
|
}
|
|
|
|
pub fn set_show_menu(&mut self, value: bool) {
|
|
self.show_menu = value;
|
|
}
|
|
|
|
pub const fn get_show_menu(&self) -> bool {
|
|
self.show_menu
|
|
}
|
|
|
|
pub const fn app_time_format(&self) -> &Option<AppTimeFormat> {
|
|
&self.app_time_format
|
|
}
|
|
|
|
pub const fn set_app_time_format(&mut self, value: Option<AppTimeFormat>) {
|
|
self.app_time_format = value;
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Footer {
|
|
pub running_clock: bool,
|
|
pub selected_content: Content,
|
|
pub app_edit_mode: AppEditMode,
|
|
pub app_time: AppTime,
|
|
}
|
|
|
|
impl StatefulWidget for Footer {
|
|
type State = FooterState;
|
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
|
let content_labels: BTreeMap<Content, &str> = BTreeMap::from([
|
|
(Content::Countdown, "[c]ountdown"),
|
|
(Content::Timer, "[t]imer"),
|
|
(Content::Pomodoro, "[p]omodoro"),
|
|
(Content::LocalTime, "[l]ocal time"),
|
|
]);
|
|
|
|
let [_, area] =
|
|
Layout::horizontal([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
|
|
|
|
let [border_area, menu_area] =
|
|
Layout::vertical([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
|
|
|
|
Block::new()
|
|
.borders(Borders::TOP)
|
|
.title(
|
|
format! {"[m]enu {:} ", if state.show_menu {scrollbar::VERTICAL.end} else {scrollbar::VERTICAL.begin}},
|
|
)
|
|
.title(
|
|
Line::from(
|
|
match (state.app_time_format, self.selected_content) {
|
|
// Show time
|
|
(Some(v), content) if content != Content::LocalTime => format!(" {} " // add some space around
|
|
, self.app_time.format(&v)),
|
|
// Hide time -> empty
|
|
_ => "".into(),
|
|
}
|
|
).right_aligned())
|
|
.border_set(border::PLAIN)
|
|
.render(border_area, buf);
|
|
// show menu
|
|
if state.show_menu {
|
|
let content_labels: Vec<Span> = content_labels
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, (content, label))| {
|
|
let mut style = Style::default();
|
|
// Add space for all except last
|
|
let label = if index < content_labels.len() - 1 {
|
|
format!("{label} ")
|
|
} else {
|
|
label.to_string()
|
|
};
|
|
if *content == self.selected_content {
|
|
style = style.add_modifier(Modifier::BOLD);
|
|
}
|
|
Span::styled(label, style)
|
|
})
|
|
.collect();
|
|
|
|
const SPACE: &str = " "; // 2 empty spaces
|
|
let widths = [Constraint::Length(12), Constraint::Percentage(100)];
|
|
let mut table_rows = vec![
|
|
// screens
|
|
Row::new(vec![
|
|
Cell::from(Span::styled(
|
|
"screens",
|
|
Style::default().add_modifier(Modifier::BOLD),
|
|
)),
|
|
Cell::from(Line::from(content_labels)),
|
|
]),
|
|
// appearance
|
|
Row::new(vec![
|
|
Cell::from(Span::styled(
|
|
"appearance",
|
|
Style::default().add_modifier(Modifier::BOLD),
|
|
)),
|
|
Cell::from(Line::from(vec![
|
|
Span::from("[,]change style"),
|
|
Span::from(SPACE),
|
|
Span::from("[.]toggle deciseconds"),
|
|
Span::from(SPACE),
|
|
Span::from(format!(
|
|
"[:]toggle {} time",
|
|
match self.app_time {
|
|
AppTime::Local(_) => "local",
|
|
AppTime::Utc(_) => "utc",
|
|
}
|
|
)),
|
|
])),
|
|
]),
|
|
];
|
|
|
|
if self.selected_content != Content::LocalTime {
|
|
table_rows.extend_from_slice(&[
|
|
// controls - 1. row
|
|
Row::new(vec![
|
|
Cell::from(Span::styled(
|
|
"controls",
|
|
Style::default().add_modifier(Modifier::BOLD),
|
|
)),
|
|
Cell::from(Line::from({
|
|
match self.app_edit_mode {
|
|
AppEditMode::None => {
|
|
let mut spans = vec![Span::from(if self.running_clock {
|
|
"[s]top"
|
|
} else {
|
|
"[s]tart"
|
|
})];
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[e]dit"),
|
|
]);
|
|
if self.selected_content == Content::Countdown {
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[^e]dit by local time"),
|
|
]);
|
|
}
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[r]eset clock"),
|
|
]);
|
|
if self.selected_content == Content::Pomodoro {
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[^r]eset clocks+rounds"),
|
|
]);
|
|
}
|
|
spans
|
|
}
|
|
_ => {
|
|
let mut spans = vec![Span::from("[s]ave changes")];
|
|
if self.selected_content == Content::Countdown
|
|
|| self.selected_content == Content::Pomodoro
|
|
{
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[^s]ave initial value"),
|
|
]);
|
|
}
|
|
spans.extend_from_slice(&[
|
|
Span::from(SPACE),
|
|
Span::from("[esc]skip changes"),
|
|
]);
|
|
spans
|
|
}
|
|
}
|
|
})),
|
|
]),
|
|
// controls - 2. row
|
|
Row::new(vec![
|
|
Cell::from(Line::from("")),
|
|
Cell::from(Line::from({
|
|
match self.app_edit_mode {
|
|
AppEditMode::None => {
|
|
let mut spans = vec![];
|
|
if self.selected_content == Content::Pomodoro {
|
|
spans.extend_from_slice(&[Span::from(
|
|
"[← →]switch work/pause",
|
|
)]);
|
|
}
|
|
spans
|
|
}
|
|
_ => vec![
|
|
Span::from(format!(
|
|
// ← →,
|
|
"[{} {}]change selection",
|
|
scrollbar::HORIZONTAL.begin,
|
|
scrollbar::HORIZONTAL.end
|
|
)),
|
|
Span::from(SPACE),
|
|
Span::from(format!(
|
|
// ↑
|
|
"[{}]edit up",
|
|
scrollbar::VERTICAL.begin
|
|
)),
|
|
Span::from(SPACE),
|
|
Span::from(format!(
|
|
// ↓
|
|
"[{}]edit up",
|
|
scrollbar::VERTICAL.end
|
|
)),
|
|
],
|
|
}
|
|
})),
|
|
]),
|
|
])
|
|
}
|
|
|
|
let table = Table::new(table_rows, widths).column_spacing(1);
|
|
|
|
Widget::render(table, menu_area, buf);
|
|
}
|
|
}
|
|
}
|