ClockWidget (#10)
This commit is contained in:
parent
b5f9147b45
commit
929e453d85
12
src/app.rs
12
src/app.rs
@ -3,7 +3,6 @@ use crate::{
|
|||||||
constants::TICK_VALUE_MS,
|
constants::TICK_VALUE_MS,
|
||||||
events::{Event, EventHandler, Events},
|
events::{Event, EventHandler, Events},
|
||||||
terminal::Terminal,
|
terminal::Terminal,
|
||||||
utils::center,
|
|
||||||
widgets::{
|
widgets::{
|
||||||
countdown::{Countdown, CountdownWidget},
|
countdown::{Countdown, CountdownWidget},
|
||||||
footer::Footer,
|
footer::Footer,
|
||||||
@ -17,7 +16,7 @@ use ratatui::{
|
|||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
crossterm::event::{KeyCode, KeyEvent},
|
crossterm::event::{KeyCode, KeyEvent},
|
||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
widgets::{Block, StatefulWidget, Widget},
|
widgets::{StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
@ -114,8 +113,6 @@ struct AppWidget;
|
|||||||
|
|
||||||
impl AppWidget {
|
impl AppWidget {
|
||||||
fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) {
|
fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) {
|
||||||
// center content
|
|
||||||
let area = center(area, Constraint::Length(50), Constraint::Length(2));
|
|
||||||
match state.content {
|
match state.content {
|
||||||
Content::Timer => TimerWidget.render(area, buf, &mut state.timer),
|
Content::Timer => TimerWidget.render(area, buf, &mut state.timer),
|
||||||
Content::Countdown => CountdownWidget.render(area, buf, &mut state.countdown),
|
Content::Countdown => CountdownWidget.render(area, buf, &mut state.countdown),
|
||||||
@ -129,14 +126,13 @@ impl StatefulWidget for AppWidget {
|
|||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
let vertical = Layout::vertical([
|
let vertical = Layout::vertical([
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Fill(0),
|
Constraint::Percentage(100),
|
||||||
Constraint::Length(if state.show_menu { 2 } else { 1 }),
|
Constraint::Length(if state.show_menu { 2 } else { 1 }),
|
||||||
]);
|
]);
|
||||||
let [v0, v1, v4] = vertical.areas(area);
|
let [v0, v1, v2] = vertical.areas(area);
|
||||||
|
|
||||||
Block::new().render(area, buf);
|
|
||||||
Header::new(true).render(v0, buf);
|
Header::new(true).render(v0, buf);
|
||||||
self.render_content(v1, buf, state);
|
self.render_content(v1, buf, state);
|
||||||
Footer::new(state.show_menu, state.content).render(v4, buf);
|
Footer::new(state.show_menu, state.content).render(v2, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
230
src/clock.rs
230
src/clock.rs
@ -3,6 +3,12 @@ use std::marker::PhantomData;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
|
|
||||||
|
use ratatui::{
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::{Constraint, Direction, Layout, Position, Rect},
|
||||||
|
widgets::StatefulWidget,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Initial,
|
Initial,
|
||||||
@ -115,3 +121,227 @@ impl Clock<Timer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DIGIT_SYMBOL: &str = "█";
|
||||||
|
|
||||||
|
const DIGIT_SIZE: usize = 5;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_0: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_1: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_2: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_3: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_4: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_5: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_6: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_7: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_8: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_9: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const DIGIT_ERROR: [u8; DIGIT_SIZE * DIGIT_SIZE] = [
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 0,
|
||||||
|
1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct ClockWidget<T> {
|
||||||
|
phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ClockWidget<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_horizontal_lengths(&self) -> [u16; 3] {
|
||||||
|
[11, 4, 11]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_width(&self) -> u16 {
|
||||||
|
self.get_horizontal_lengths().iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_height(&self) -> u16 {
|
||||||
|
DIGIT_SIZE as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_number(number: u64, area: Rect, buf: &mut Buffer) {
|
||||||
|
let left = area.left();
|
||||||
|
let top = area.top();
|
||||||
|
|
||||||
|
let digits = match number {
|
||||||
|
0 => DIGIT_0,
|
||||||
|
1 => DIGIT_1,
|
||||||
|
2 => DIGIT_2,
|
||||||
|
3 => DIGIT_3,
|
||||||
|
4 => DIGIT_4,
|
||||||
|
5 => DIGIT_5,
|
||||||
|
6 => DIGIT_6,
|
||||||
|
7 => DIGIT_7,
|
||||||
|
8 => DIGIT_8,
|
||||||
|
9 => DIGIT_9,
|
||||||
|
_ => DIGIT_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
digits.iter().enumerate().for_each(|(i, item)| {
|
||||||
|
let x = i % DIGIT_SIZE;
|
||||||
|
let y = i / DIGIT_SIZE;
|
||||||
|
if *item == 1 {
|
||||||
|
let p = Position {
|
||||||
|
x: left + x as u16,
|
||||||
|
y: top + y as u16,
|
||||||
|
};
|
||||||
|
if let Some(cell) = buf.cell_mut(p) {
|
||||||
|
cell.set_symbol(DIGIT_SYMBOL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_digit_pair(d: u64, area: Rect, buf: &mut Buffer) {
|
||||||
|
let h = Layout::new(
|
||||||
|
Direction::Horizontal,
|
||||||
|
Constraint::from_lengths([DIGIT_SIZE as u16, 2, DIGIT_SIZE as u16]),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
Self::render_number(d / 10, h[0], buf);
|
||||||
|
Self::render_number(d % 10, h[2], buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_colon(area: Rect, buf: &mut Buffer) {
|
||||||
|
let left = area.left();
|
||||||
|
let top = area.top();
|
||||||
|
|
||||||
|
let positions = [
|
||||||
|
Position {
|
||||||
|
x: left + 1,
|
||||||
|
y: top + 1,
|
||||||
|
},
|
||||||
|
Position {
|
||||||
|
x: left + 2,
|
||||||
|
y: top + 1,
|
||||||
|
},
|
||||||
|
Position {
|
||||||
|
x: left + 1,
|
||||||
|
y: top + 3,
|
||||||
|
},
|
||||||
|
Position {
|
||||||
|
x: left + 2,
|
||||||
|
y: top + 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for pos in positions {
|
||||||
|
if let Some(cell) = buf.cell_mut(pos) {
|
||||||
|
cell.set_symbol(DIGIT_SYMBOL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StatefulWidget for ClockWidget<T> {
|
||||||
|
type State = Clock<T>;
|
||||||
|
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
|
// center
|
||||||
|
let [_, h, _] = Layout::horizontal([
|
||||||
|
Constraint::Fill(0),
|
||||||
|
Constraint::Length(self.get_width()),
|
||||||
|
Constraint::Fill(0),
|
||||||
|
])
|
||||||
|
.areas(area);
|
||||||
|
|
||||||
|
let [h1, h2, h3] = Layout::new(
|
||||||
|
Direction::Horizontal,
|
||||||
|
Constraint::from_lengths(self.get_horizontal_lengths()),
|
||||||
|
)
|
||||||
|
.areas(h);
|
||||||
|
|
||||||
|
Self::render_digit_pair(state.minutes(), h1, buf);
|
||||||
|
Self::render_colon(h2, buf);
|
||||||
|
Self::render_digit_pair(state.seconds(), h3, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
20
src/utils.rs
20
src/utils.rs
@ -1,11 +1,25 @@
|
|||||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||||
|
|
||||||
/// Helper to center an area by given `Constraint`'s
|
/// Helper to center an area horizontally by given `Constraint`
|
||||||
/// based on [Center a Rect](https://ratatui.rs/recipes/layout/center-a-rect)
|
/// based on [Center a Rect](https://ratatui.rs/recipes/layout/center-a-rect)
|
||||||
pub fn center(base_area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
|
pub fn center_horizontal(base_area: Rect, horizontal: Constraint) -> Rect {
|
||||||
let [area] = Layout::horizontal([horizontal])
|
let [area] = Layout::horizontal([horizontal])
|
||||||
.flex(Flex::Center)
|
.flex(Flex::Center)
|
||||||
.areas(base_area);
|
.areas(base_area);
|
||||||
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
|
|
||||||
area
|
area
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to center an area vertically by given `Constraint`
|
||||||
|
/// based on [Center a Rect](https://ratatui.rs/recipes/layout/center-a-rect)
|
||||||
|
pub fn center_vertical(base_area: Rect, vertical: Constraint) -> Rect {
|
||||||
|
let [area] = Layout::vertical([vertical])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(base_area);
|
||||||
|
area
|
||||||
|
}
|
||||||
|
/// Helper to center an area by given `Constraint`'s
|
||||||
|
/// based on [Center a Rect](https://ratatui.rs/recipes/layout/center-a-rect)
|
||||||
|
pub fn center(base_area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
|
||||||
|
let area = center_horizontal(base_area, horizontal);
|
||||||
|
center_vertical(area, vertical)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,12 +2,15 @@ use ratatui::{
|
|||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
crossterm::event::KeyCode,
|
crossterm::event::KeyCode,
|
||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
widgets::{Paragraph, StatefulWidget, Widget},
|
text::Line,
|
||||||
|
widgets::{StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::{self, Clock},
|
clock::{self, Clock, ClockWidget},
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
|
utils::center,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -41,14 +44,21 @@ impl EventHandler for Countdown {
|
|||||||
|
|
||||||
pub struct CountdownWidget;
|
pub struct CountdownWidget;
|
||||||
|
|
||||||
impl StatefulWidget for &CountdownWidget {
|
impl StatefulWidget for CountdownWidget {
|
||||||
type State = Countdown;
|
type State = Countdown;
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
let h = Paragraph::new(state.headline.clone()).centered();
|
let clock = ClockWidget::new();
|
||||||
let c = Paragraph::new(format!("{}", state.clock)).centered();
|
let headline = Line::raw(state.headline.clone());
|
||||||
let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
|
||||||
|
|
||||||
h.render(v1, buf);
|
let area = center(
|
||||||
c.render(v2, buf);
|
area,
|
||||||
|
Constraint::Length(max(clock.get_width(), headline.width() as u16)),
|
||||||
|
Constraint::Length(clock.get_height() + 2),
|
||||||
|
);
|
||||||
|
let [v1, _, v2] =
|
||||||
|
Layout::vertical(Constraint::from_lengths([clock.get_height(), 1, 1])).areas(area);
|
||||||
|
|
||||||
|
clock.render(v1, buf, &mut state.clock);
|
||||||
|
headline.centered().render(v2, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::Rect,
|
layout::{Constraint, Layout, Rect},
|
||||||
widgets::{Paragraph, Widget},
|
style::Stylize,
|
||||||
|
text::Line,
|
||||||
|
widgets::Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::utils::center;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct Pomodoro {
|
pub struct Pomodoro {
|
||||||
headline: String,
|
headline: String,
|
||||||
@ -17,7 +21,17 @@ impl Pomodoro {
|
|||||||
|
|
||||||
impl Widget for Pomodoro {
|
impl Widget for Pomodoro {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let h = Paragraph::new(self.headline).centered();
|
let headline = Line::raw(self.headline.clone());
|
||||||
h.render(area, buf);
|
|
||||||
|
let area = center(
|
||||||
|
area,
|
||||||
|
Constraint::Length(headline.width() as u16),
|
||||||
|
Constraint::Length(3),
|
||||||
|
);
|
||||||
|
|
||||||
|
let [v1, _, v2] = Layout::vertical(Constraint::from_lengths([1, 1, 1])).areas(area);
|
||||||
|
|
||||||
|
headline.render(v2, buf);
|
||||||
|
Line::raw("SOON").centered().italic().render(v1, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
|
use crate::{
|
||||||
|
clock::{self, Clock, ClockWidget},
|
||||||
|
events::{Event, EventHandler},
|
||||||
|
utils::center,
|
||||||
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
crossterm::event::KeyCode,
|
crossterm::event::KeyCode,
|
||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
widgets::{Paragraph, StatefulWidget, Widget},
|
text::Line,
|
||||||
};
|
widgets::{StatefulWidget, Widget},
|
||||||
|
|
||||||
use crate::{
|
|
||||||
clock::{self, Clock},
|
|
||||||
events::{Event, EventHandler},
|
|
||||||
};
|
};
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
@ -41,14 +43,21 @@ impl EventHandler for Timer {
|
|||||||
|
|
||||||
pub struct TimerWidget;
|
pub struct TimerWidget;
|
||||||
|
|
||||||
impl StatefulWidget for TimerWidget {
|
impl StatefulWidget for &TimerWidget {
|
||||||
type State = Timer;
|
type State = Timer;
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
let h = Paragraph::new(state.headline.clone()).centered();
|
let clock = ClockWidget::new();
|
||||||
let c = Paragraph::new(format!("{}", state.clock)).centered();
|
let headline = Line::raw(state.headline.clone());
|
||||||
let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
|
||||||
|
|
||||||
h.render(v1, buf);
|
let area = center(
|
||||||
c.render(v2, buf)
|
area,
|
||||||
|
Constraint::Length(max(clock.get_width(), headline.width() as u16)),
|
||||||
|
Constraint::Length(clock.get_height() + 2),
|
||||||
|
);
|
||||||
|
let [v1, _, v2] =
|
||||||
|
Layout::vertical(Constraint::from_lengths([clock.get_height(), 1, 1])).areas(area);
|
||||||
|
|
||||||
|
clock.render(v1, buf, &mut state.clock);
|
||||||
|
headline.centered().render(v2, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user