timr-tui/src/widgets/clock.rs
Jens K. ae1a48e5e8
Progressbar (#25)
* Progressbar
* header
* label
* border
2024-12-25 12:21:36 +01:00

1044 lines
31 KiB
Rust

use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::marker::PhantomData;
use std::time::Duration;
use strum::Display;
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Position, Rect},
symbols::shade,
widgets::StatefulWidget,
};
use crate::utils::center_horizontal;
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
pub enum Time {
Seconds,
Minutes,
Hours,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mode {
Initial,
Tick,
Pause,
Editable(
Time,
Box<Mode>, /* previous mode before starting editing */
),
Done,
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Mode::Initial => write!(f, "[]"),
Mode::Tick => write!(f, ">"),
Mode::Pause => write!(f, "||"),
Mode::Editable(time, _) => match time {
Time::Seconds => write!(f, "[edit seconds]"),
Time::Minutes => write!(f, "[edit minutes]"),
Time::Hours => write!(f, "[edit hours]"),
},
Mode::Done => write!(f, "done"),
}
}
}
// unstable
// https://doc.rust-lang.org/src/core/time.rs.html#32
const SECS_PER_MINUTE: u64 = 60;
// unstable
// https://doc.rust-lang.org/src/core/time.rs.html#34
const MINS_PER_HOUR: u64 = 60;
// unstable
// https://doc.rust-lang.org/src/core/time.rs.html#36
const HOURS_PER_DAY: u64 = 24;
// max. 99:59:59
const MAX_DURATION: Duration =
Duration::from_secs(100 * MINS_PER_HOUR * SECS_PER_MINUTE).saturating_sub(ONE_SECOND);
const ONE_SECOND: Duration = Duration::from_secs(1);
const ONE_MINUTE: Duration = Duration::from_secs(SECS_PER_MINUTE);
const ONE_HOUR: Duration = Duration::from_secs(MINS_PER_HOUR * SECS_PER_MINUTE);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Display, PartialOrd, Ord)]
pub enum Format {
S,
Ss,
MSs,
MmSs,
HMmSs,
HhMmSs,
}
#[derive(Debug, Copy, Clone, ValueEnum, Default, Serialize, Deserialize)]
pub enum Style {
#[default]
#[value(name = "full", alias = "f")]
Full,
#[value(name = "light", alias = "l")]
Light,
#[value(name = "medium", alias = "m")]
Medium,
#[value(name = "dark", alias = "d")]
Dark,
#[value(name = "thick", alias = "t")]
Thick,
#[value(name = "cross", alias = "c")]
Cross,
/// https://en.wikipedia.org/wiki/Braille_Patterns
/// Note: Might not be supported in all terminals
/// see https://docs.rs/ratatui/latest/src/ratatui/symbols.rs.html#150
#[value(name = "braille", alias = "b")]
Braille,
}
impl Style {
pub fn next(&self) -> Self {
match self {
Style::Full => Style::Dark,
Style::Dark => Style::Medium,
Style::Medium => Style::Light,
Style::Light => Style::Braille,
Style::Braille => Style::Thick,
Style::Thick => Style::Cross,
Style::Cross => Style::Full,
}
}
}
#[derive(Debug, Clone)]
pub struct Clock<T> {
pub initial_value: Duration,
pub current_value: Duration,
tick_value: Duration,
mode: Mode,
format: Format,
pub style: Style,
pub with_decis: bool,
phantom: PhantomData<T>,
}
pub struct ClockArgs {
pub initial_value: Duration,
pub current_value: Duration,
pub tick_value: Duration,
pub style: Style,
pub with_decis: bool,
}
impl<T> Clock<T> {
pub fn toggle_pause(&mut self) {
self.mode = if self.mode == Mode::Tick {
Mode::Pause
} else {
Mode::Tick
}
}
pub fn toggle_edit(&mut self) {
self.mode = match self.mode.clone() {
Mode::Editable(_, prev) => {
let p = *prev;
// special cases: Should `Mode` be updated?
// 1. `Done` -> `Initial` ?
if p == Mode::Done && self.current_value.gt(&Duration::ZERO) {
Mode::Initial
}
// 2. `_` -> `Done` ?
else if p != Mode::Done && self.current_value.eq(&Duration::ZERO) {
Mode::Done
}
// 3. `_` -> `_` (no change)
else {
p
}
}
mode => {
if self.format <= Format::Ss {
Mode::Editable(Time::Seconds, Box::new(mode))
} else {
Mode::Editable(Time::Minutes, Box::new(mode))
}
}
};
}
pub fn edit_current_up(&mut self) {
self.current_value = match self.mode {
Mode::Editable(Time::Seconds, _) => {
if self
.current_value
// < 99:59:58
.le(&MAX_DURATION.saturating_sub(ONE_SECOND))
{
self.current_value.saturating_add(ONE_SECOND)
} else {
self.current_value
}
}
Mode::Editable(Time::Minutes, _) => {
if self
.current_value
// < 99:58:59
.le(&MAX_DURATION.saturating_sub(ONE_MINUTE))
{
self.current_value.saturating_add(ONE_MINUTE)
} else {
self.current_value
}
}
Mode::Editable(Time::Hours, _) => {
if self
.current_value
// < 98:59:59
.lt(&MAX_DURATION.saturating_sub(ONE_HOUR))
{
self.current_value.saturating_add(ONE_HOUR)
} else {
self.current_value
}
}
_ => self.current_value,
};
self.update_format();
}
pub fn edit_current_down(&mut self) {
self.current_value = match self.mode {
Mode::Editable(Time::Seconds, _) => self.current_value.saturating_sub(ONE_SECOND),
Mode::Editable(Time::Minutes, _) => self.current_value.saturating_sub(ONE_MINUTE),
Mode::Editable(Time::Hours, _) => self.current_value.saturating_sub(ONE_HOUR),
_ => self.current_value,
};
self.update_format();
self.update_mode();
}
pub fn get_mode(&self) -> &Mode {
&self.mode
}
pub fn is_running(&self) -> bool {
self.mode == Mode::Tick
}
pub fn is_edit_mode(&self) -> bool {
matches!(self.mode, Mode::Editable(_, _))
}
fn edit_mode_next(&mut self) {
let mode = self.mode.clone();
self.mode = match mode {
Mode::Editable(Time::Seconds, prev) if self.format >= Format::MSs => {
Mode::Editable(Time::Minutes, prev)
}
Mode::Editable(Time::Minutes, prev) if self.format <= Format::MmSs => {
Mode::Editable(Time::Seconds, prev)
}
Mode::Editable(Time::Minutes, prev) if self.format >= Format::MmSs => {
Mode::Editable(Time::Hours, prev)
}
Mode::Editable(Time::Hours, prev) => Mode::Editable(Time::Seconds, prev),
_ => mode,
};
self.update_format();
}
fn edit_mode_prev(&mut self) {
let mode = self.mode.clone();
self.mode = match mode {
Mode::Editable(Time::Seconds, prev) if self.format >= Format::HMmSs => {
Mode::Editable(Time::Hours, prev)
}
Mode::Editable(Time::Seconds, prev) if self.format <= Format::MmSs => {
Mode::Editable(Time::Minutes, prev)
}
Mode::Editable(Time::Minutes, prev) => Mode::Editable(Time::Seconds, prev),
Mode::Editable(Time::Hours, prev) => Mode::Editable(Time::Minutes, prev),
_ => mode,
};
self.update_format();
}
fn update_mode(&mut self) {
let mode = self.mode.clone();
self.mode = match mode {
Mode::Editable(Time::Hours, prev) if self.format <= Format::MmSs => {
Mode::Editable(Time::Minutes, prev)
}
Mode::Editable(Time::Minutes, prev) if self.format <= Format::Ss => {
Mode::Editable(Time::Seconds, prev)
}
_ => mode,
}
}
pub fn reset(&mut self) {
self.mode = Mode::Initial;
self.current_value = self.initial_value;
self.update_format();
}
fn current_hours(&self) -> u64 {
self.current_seconds() / (SECS_PER_MINUTE * MINS_PER_HOUR)
}
fn current_hours_mod(&self) -> u64 {
self.current_hours() % HOURS_PER_DAY
}
fn current_minutes(&self) -> u64 {
self.current_seconds() / MINS_PER_HOUR
}
fn current_minutes_mod(&self) -> u64 {
self.current_minutes() % SECS_PER_MINUTE
}
fn current_seconds(&self) -> u64 {
self.current_value.as_secs()
}
fn current_seconds_mod(&self) -> u64 {
self.current_seconds() % SECS_PER_MINUTE
}
// deciseconds
fn current_decis(&self) -> u64 {
(self.current_value.subsec_millis() / 100) as u64
}
pub fn is_done(&self) -> bool {
self.mode == Mode::Done
}
fn update_format(&mut self) {
self.format = self.get_format();
}
pub fn get_format(&self) -> Format {
if self.current_hours() >= 10 {
Format::HhMmSs
} else if self.current_hours() >= 1 {
Format::HMmSs
} else if self.current_minutes() >= 10 {
Format::MmSs
} else if self.current_minutes() >= 1 {
Format::MSs
} else if self.current_seconds() >= 10 {
Format::Ss
} else {
Format::S
}
}
}
impl<T> fmt::Display for Clock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02}:{:02}:{:02}.{}",
self.current_hours_mod(),
self.current_minutes_mod(),
self.current_seconds_mod(),
self.current_decis()
)
}
}
#[derive(Debug, Clone)]
pub struct Countdown {}
impl Clock<Countdown> {
pub fn new(args: ClockArgs) -> Self {
let ClockArgs {
initial_value,
current_value,
tick_value,
style,
with_decis,
} = args;
let mut instance = Self {
initial_value,
current_value,
tick_value,
mode: if current_value == Duration::ZERO {
Mode::Done
} else if current_value == initial_value {
Mode::Initial
} else {
Mode::Pause
},
format: Format::S,
style,
with_decis,
phantom: PhantomData,
};
// update format once
instance.update_format();
instance
}
pub fn tick(&mut self) {
if self.mode == Mode::Tick {
self.current_value = self.current_value.saturating_sub(self.tick_value);
self.set_done();
self.update_format();
}
}
fn set_done(&mut self) {
if self.current_value.is_zero() {
self.mode = Mode::Done;
}
}
pub fn get_percentage_done(&self) -> u16 {
let initial = self.initial_value.as_millis();
let elapsed = self
.initial_value
.saturating_sub(self.current_value)
.as_millis();
(elapsed * 100 / initial) as u16
}
pub fn edit_next(&mut self) {
self.edit_mode_next();
}
pub fn edit_prev(&mut self) {
self.edit_mode_prev();
}
pub fn edit_up(&mut self) {
self.edit_current_up();
// update `initial_value` if needed
if self.initial_value.lt(&self.current_value) {
self.initial_value = self.current_value;
}
}
pub fn edit_down(&mut self) {
self.edit_current_down();
// update `initial_value` if needed
if self.initial_value.gt(&self.current_value) {
self.initial_value = self.current_value;
}
}
}
#[derive(Debug, Clone)]
pub struct Timer {}
impl Clock<Timer> {
pub fn new(args: ClockArgs) -> Self {
let ClockArgs {
initial_value,
current_value,
tick_value,
style,
with_decis,
} = args;
let mut instance = Self {
initial_value,
current_value,
tick_value,
mode: if current_value == initial_value {
Mode::Initial
} else if current_value >= MAX_DURATION {
Mode::Done
} else {
Mode::Pause
},
format: Format::S,
phantom: PhantomData,
style,
with_decis,
};
// update format once
instance.update_format();
instance
}
pub fn tick(&mut self) {
if self.mode == Mode::Tick {
self.current_value = self.current_value.saturating_add(self.tick_value);
self.set_done();
self.update_format();
}
}
fn set_done(&mut self) {
if self.current_value >= MAX_DURATION {
self.mode = Mode::Done;
}
}
pub fn edit_next(&mut self) {
self.edit_mode_next();
}
pub fn edit_prev(&mut self) {
self.edit_mode_prev();
}
pub fn edit_up(&mut self) {
self.edit_current_up();
}
pub fn edit_down(&mut self) {
self.edit_current_down();
}
}
const DIGIT_SIZE: usize = 5;
const DIGIT_WIDTH: u16 = DIGIT_SIZE as u16;
const DIGIT_HEIGHT: u16 = DIGIT_SIZE as u16 + 1 /* border height */;
const COLON_WIDTH: u16 = 4; // incl. padding left + padding right
const SPACE_WIDTH: u16 = 1;
#[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>
where
T: std::fmt::Debug,
{
phantom: PhantomData<T>,
}
impl<T> ClockWidget<T>
where
T: std::fmt::Debug,
{
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
fn get_digit_symbol(&self, style: &Style) -> &str {
match &style {
Style::Full => shade::FULL,
Style::Light => shade::LIGHT,
Style::Medium => shade::MEDIUM,
Style::Dark => shade::DARK,
Style::Cross => "",
Style::Thick => "",
Style::Braille => "",
}
}
fn get_horizontal_lengths(&self, format: &Format, with_decis: bool) -> Vec<u16> {
let add_decis = |mut lengths: Vec<u16>, with_decis: bool| -> Vec<u16> {
if with_decis {
lengths.extend_from_slice(&[
COLON_WIDTH, // .
DIGIT_WIDTH, // ds
])
}
lengths
};
match format {
Format::HhMmSs => add_decis(
vec![
DIGIT_WIDTH, // h
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // h
COLON_WIDTH, // :
DIGIT_WIDTH, // m
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m
COLON_WIDTH, // :
DIGIT_WIDTH, // s
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
],
with_decis,
),
Format::HMmSs => add_decis(
vec![
DIGIT_WIDTH, // h
COLON_WIDTH, // :
DIGIT_WIDTH, // m
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m
COLON_WIDTH, // :
DIGIT_WIDTH, // s
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
],
with_decis,
),
Format::MmSs => add_decis(
vec![
DIGIT_WIDTH, // m
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m
COLON_WIDTH, // :
DIGIT_WIDTH, // s
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
],
with_decis,
),
Format::MSs => add_decis(
vec![
DIGIT_WIDTH, // m
COLON_WIDTH, // :
DIGIT_WIDTH, // s
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
],
with_decis,
),
Format::Ss => add_decis(
vec![
DIGIT_WIDTH, // s
SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
],
with_decis,
),
Format::S => add_decis(
vec![
DIGIT_WIDTH, // s
],
with_decis,
),
}
}
pub fn get_width(&self, format: &Format, with_decis: bool) -> u16 {
self.get_horizontal_lengths(format, with_decis).iter().sum()
}
pub fn get_height(&self) -> u16 {
DIGIT_HEIGHT
}
fn render_digit(
&self,
number: u64,
symbol: &str,
with_border: bool,
area: Rect,
buf: &mut Buffer,
) {
let left = area.left();
let top = area.top();
let symbols = 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,
};
symbols.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(symbol);
}
}
});
// Add border at the bottom
if with_border {
for x in 0..area.width {
let p = Position {
x: left + x,
y: top + area.height - 1,
};
if let Some(cell) = buf.cell_mut(p) {
cell.set_symbol("");
}
}
}
}
fn render_colon(&self, symbol: &str, 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(symbol);
}
}
}
fn render_dot(&self, symbol: &str, area: Rect, buf: &mut Buffer) {
let positions = [
Position {
x: area.left() + 1,
y: area.top() + area.height - 2,
},
Position {
x: area.left() + 2,
y: area.top() + area.height - 2,
},
];
for pos in positions {
if let Some(cell) = buf.cell_mut(pos) {
cell.set_symbol(symbol);
}
}
}
}
impl<T> StatefulWidget for ClockWidget<T>
where
T: std::fmt::Debug,
{
type State = Clock<T>;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let with_decis = state.with_decis;
let format = state.format;
let symbol = self.get_digit_symbol(&state.style);
let widths = self.get_horizontal_lengths(&format, with_decis);
let area = center_horizontal(
area,
Constraint::Length(self.get_width(&format, with_decis)),
);
let edit_hours = matches!(state.mode, Mode::Editable(Time::Hours, _));
let edit_minutes = matches!(state.mode, Mode::Editable(Time::Minutes, _));
let edit_secs = matches!(state.mode, Mode::Editable(Time::Seconds, _));
match format {
Format::HhMmSs if with_decis => {
let [hh, _, h, c_hm, mm, _, m, c_ms, ss, _, s, d, ds] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_hours() / 10, symbol, edit_hours, hh, buf);
self.render_digit(state.current_hours() % 10, symbol, edit_hours, h, buf);
self.render_colon(symbol, c_hm, buf);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::HhMmSs => {
let [hh, _, h, c_hm, mm, _, m, c_ms, ss, _, s] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_hours() / 10, symbol, edit_hours, hh, buf);
self.render_digit(state.current_hours() % 10, symbol, edit_hours, h, buf);
self.render_colon(symbol, c_hm, buf);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
Format::HMmSs if with_decis => {
let [h, c_hm, mm, _, m, c_ms, ss, _, s, d, ds] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_hours() % 10, symbol, edit_hours, h, buf);
self.render_colon(symbol, c_hm, buf);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::HMmSs => {
let [h, c_hm, mm, _, m, c_ms, ss, _, s] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_hours() % 10, symbol, edit_hours, h, buf);
self.render_colon(symbol, c_hm, buf);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
Format::MmSs if with_decis => {
let [mm, _, m, c_ms, ss, _, s, d, ds] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::MmSs => {
let [mm, _, m, c_ms, ss, _, s] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(
state.current_minutes_mod() / 10,
symbol,
edit_minutes,
mm,
buf,
);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
Format::MSs if with_decis => {
let [m, c_ms, ss, _, s, d, ds] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::MSs => {
let [m, c_ms, ss, _, s] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(
state.current_minutes_mod() % 10,
symbol,
edit_minutes,
m,
buf,
);
self.render_colon(symbol, c_ms, buf);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
Format::Ss if state.with_decis => {
let [ss, _, s, d, ds] =
Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::Ss => {
let [ss, _, s] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_seconds_mod() / 10, symbol, edit_secs, ss, buf);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
Format::S if with_decis => {
let [s, d, ds] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
self.render_dot(symbol, d, buf);
self.render_digit(state.current_decis(), symbol, false, ds, buf);
}
Format::S => {
let [s] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area);
self.render_digit(state.current_seconds_mod() % 10, symbol, edit_secs, s, buf);
}
}
}
}