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, /* 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 { pub initial_value: Duration, pub current_value: Duration, tick_value: Duration, mode: Mode, format: Format, pub style: Style, pub with_decis: bool, phantom: PhantomData, } pub struct ClockArgs { pub initial_value: Duration, pub current_value: Duration, pub tick_value: Duration, pub style: Style, pub with_decis: bool, } impl Clock { 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 fmt::Display for Clock { 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 { 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 { 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 where T: std::fmt::Debug, { phantom: PhantomData, } impl ClockWidget 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 { let add_decis = |mut lengths: Vec, with_decis: bool| -> Vec { 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 StatefulWidget for ClockWidget where T: std::fmt::Debug, { type State = Clock; 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); } } } }