From 25d6795d7efa38cb92b98b8bfe70eb3e3b944537 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 5 Sep 2022 21:01:43 +0800 Subject: [PATCH] extract a font layer and reimplement the bricks font (#23) * implement the font layer * run fmt * refactor --- clock-tui/src/app/modes.rs | 2 +- clock-tui/src/app/modes/clock.rs | 2 +- clock-tui/src/app/modes/countdown.rs | 2 +- clock-tui/src/app/modes/stopwatch.rs | 2 +- clock-tui/src/app/modes/timer.rs | 2 +- clock-tui/src/bin/main.rs | 2 +- clock-tui/src/bricks_text/chars.rs | 265 ------------------ .../src/{bricks_text.rs => clock_text.rs} | 18 +- clock-tui/src/clock_text/font.rs | 10 + clock-tui/src/clock_text/font/bricks.rs | 121 ++++++++ clock-tui/src/clock_text/point.rs | 20 ++ clock-tui/src/lib.rs | 2 +- xtask/src/main.rs | 6 +- 13 files changed, 171 insertions(+), 283 deletions(-) delete mode 100644 clock-tui/src/bricks_text/chars.rs rename clock-tui/src/{bricks_text.rs => clock_text.rs} (74%) create mode 100644 clock-tui/src/clock_text/font.rs create mode 100644 clock-tui/src/clock_text/font/bricks.rs create mode 100644 clock-tui/src/clock_text/point.rs diff --git a/clock-tui/src/app/modes.rs b/clock-tui/src/app/modes.rs index 6f980b9..16a4a8e 100644 --- a/clock-tui/src/app/modes.rs +++ b/clock-tui/src/app/modes.rs @@ -6,9 +6,9 @@ mod timer; use std::cmp::min; use std::fmt::Write as _; +use crate::clock_text::BricksText; use chrono::Duration; pub(crate) use clock::Clock; -use crate::bricks_text::BricksText; pub(crate) use countdown::Countdown; pub(crate) use stopwatch::Stopwatch; pub(crate) use timer::Timer; diff --git a/clock-tui/src/app/modes/clock.rs b/clock-tui/src/app/modes/clock.rs index 97fd0d9..f44a772 100644 --- a/clock-tui/src/app/modes/clock.rs +++ b/clock-tui/src/app/modes/clock.rs @@ -1,6 +1,6 @@ +use crate::clock_text::BricksText; use chrono::{Local, Utc}; use chrono_tz::Tz; -use crate::bricks_text::BricksText; use tui::{layout::Rect, style::Style, widgets::Widget}; use super::render_centered; diff --git a/clock-tui/src/app/modes/countdown.rs b/clock-tui/src/app/modes/countdown.rs index ca8b4ac..bb9df82 100644 --- a/clock-tui/src/app/modes/countdown.rs +++ b/clock-tui/src/app/modes/countdown.rs @@ -1,5 +1,5 @@ +use crate::clock_text::BricksText; use chrono::{DateTime, Duration, Local}; -use crate::bricks_text::BricksText; use tui::{style::Style, widgets::Widget}; use super::{format_duration, render_centered, DurationFormat}; diff --git a/clock-tui/src/app/modes/stopwatch.rs b/clock-tui/src/app/modes/stopwatch.rs index 06f0aa9..c11d9ca 100644 --- a/clock-tui/src/app/modes/stopwatch.rs +++ b/clock-tui/src/app/modes/stopwatch.rs @@ -1,5 +1,5 @@ +use crate::clock_text::BricksText; use chrono::{DateTime, Duration, Local}; -use crate::bricks_text::BricksText; use tui::{buffer::Buffer, layout::Rect, style::Style, widgets::Widget}; use crate::app::Pause; diff --git a/clock-tui/src/app/modes/timer.rs b/clock-tui/src/app/modes/timer.rs index fcd0a50..dd567a8 100644 --- a/clock-tui/src/app/modes/timer.rs +++ b/clock-tui/src/app/modes/timer.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, process::Command}; +use crate::clock_text::BricksText; use chrono::{DateTime, Duration, Local}; -use crate::bricks_text::BricksText; use tui::{buffer::Buffer, layout::Rect, style::Style, widgets::Widget}; use crate::app::Pause; diff --git a/clock-tui/src/bin/main.rs b/clock-tui/src/bin/main.rs index 02b53fa..b6d354e 100644 --- a/clock-tui/src/bin/main.rs +++ b/clock-tui/src/bin/main.rs @@ -4,8 +4,8 @@ use std::{ time::{Duration, Instant}, }; -use clock_tui::app::Mode; use clap::Parser; +use clock_tui::app::Mode; use crossterm::{ event::{self, Event, KeyCode}, execute, diff --git a/clock-tui/src/bricks_text/chars.rs b/clock-tui/src/bricks_text/chars.rs deleted file mode 100644 index 714dae8..0000000 --- a/clock-tui/src/bricks_text/chars.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::{cmp::max, ops}; -use tui::{buffer::Buffer, layout::Rect, style::Style}; - -pub(crate) trait BricksChar { - fn render(&self, size: u16, style: Style, area: Rect, buf: &mut Buffer); -} - -pub struct BrickChar(char); - -impl BrickChar { - const H_UNIT: u16 = 2; - const V_UNIT: u16 = 1; - const UNIT_SIZE: Point = Point(3 * Self::H_UNIT, 5 * Self::V_UNIT); - - pub(crate) fn size(size: u16) -> Point { - Self::UNIT_SIZE * size - } - - pub(crate) fn from(char: char) -> BrickChar { - BrickChar(char) - } - - pub(crate) fn render(&self, size: u16, style: Style, area: Rect, buf: &mut Buffer) { - let char_size = BrickChar::size(size); - match self.0 { - '0'..='9' => Self::draw_digital(self.0, size, style, area, buf), - ':' => { - let start_x = area.x + size * Self::H_UNIT; - let end_x = area.x + char_size.0 - size * Self::H_UNIT; - let start_y = area.y + size * Self::V_UNIT; - let start_y2 = area.y + (char_size.1 + size * Self::V_UNIT) / 2; - let len = (char_size.1 - 3 * size * Self::V_UNIT) / 2; - for x in (start_x..end_x).step_by((size * Self::H_UNIT) as usize) { - Self::draw_line( - size, - Point(x, start_y), - len, - LineDir::Vertical, - style, - &area, - buf, - ); - Self::draw_line( - size, - Point(x, start_y2), - len, - LineDir::Vertical, - style, - &area, - buf, - ); - } - } - '-' => { - let x = area.x; - let y = area.y + (char_size.1 - size * Self::V_UNIT) / 2; - Self::draw_line( - size, - Point(x, y), - char_size.1, - LineDir::Horizontal, - style, - &area, - buf, - ); - } - '.' => { - let x = area.x + char_size.0 - size * Self::H_UNIT; - let y = area.y + char_size.1 - size * Self::V_UNIT; - Self::draw_line( - size, - Point(x, y), - size * Self::H_UNIT, - LineDir::Horizontal, - style, - &area, - buf, - ); - } - _ => {} - } - } - - fn draw_digital(d: char, size: u16, style: Style, area: Rect, buf: &mut Buffer) { - let char_size = BrickChar::size(size); - let mut draw_line = - |x, y, len, dir| Self::draw_line(size, Point(x, y), len, dir, style, &area, buf); - let x_start = area.x; - let x_end = area.x + char_size.0 - size * Self::H_UNIT; - let y_start = area.y; - let y_end = area.y + char_size.1 - size * Self::V_UNIT; - let y_center = area.y + (char_size.1 - size * Self::V_UNIT) / 2; - let half_h = (char_size.1 + size * Self::V_UNIT) / 2; - match d { - '0' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '1' => { - // draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - // draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '2' => { - // draw_line(x_start, y_start, half_h, LineDir::Vertical); - draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - // draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '3' => { - // draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '4' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - // draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '5' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - // draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '6' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - draw_line(x_start, y_center, half_h, LineDir::Vertical); - // draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '7' => { - // draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - // draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '8' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - '9' => { - draw_line(x_start, y_start, half_h, LineDir::Vertical); - // draw_line(x_start, y_center, half_h, LineDir::Vertical); - draw_line(x_end, y_start, half_h, LineDir::Vertical); - draw_line(x_end, y_center, half_h, LineDir::Vertical); - - draw_line(x_start, y_start, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_center, char_size.0, LineDir::Horizontal); - draw_line(x_start, y_end, char_size.0, LineDir::Horizontal); - } - _ => {} - } - } - - fn draw_line( - size: u16, - start: Point, - len: u16, - dir: LineDir, - style: Style, - area: &Rect, - buf: &mut Buffer, - ) { - let step = match dir { - LineDir::Horizontal => Point(Self::H_UNIT, 0), - LineDir::Vertical => Point(0, Self::V_UNIT), - }; - - let line = match dir { - LineDir::Horizontal => Point(0, Self::V_UNIT), - LineDir::Vertical => Point(Self::H_UNIT, 0), - }; - - let mut from = start; - for _ in 0..size { - let mut p = from; - for _ in (0..len).step_by(max(step.0, step.1).into()) { - if !p.in_area(area) { - break; - } - // println!("p = {:?} area = {:?}", p, area); - buf.get_mut(p.0, p.1).set_symbol("██").set_style(style); - p = p + &step; - } - from = from + &line; - } - } -} - -enum LineDir { - Horizontal, - Vertical, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct Point(pub u16, pub u16); - -impl Point { - pub(crate) fn in_area(&self, area: &Rect) -> bool { - area.left() <= self.0 - && self.0 < area.right() - && area.top() <= self.1 - && self.1 < area.bottom() - } -} - -impl ops::Add<&Point> for Point { - type Output = Point; - - fn add(self, other: &Point) -> Point { - Point(self.0 + other.0, self.1 + other.1) - } -} - -impl ops::Mul for Point { - type Output = Point; - - fn mul(self, other: u16) -> Point { - Point(self.0 * other, self.1 * other) - } -} diff --git a/clock-tui/src/bricks_text.rs b/clock-tui/src/clock_text.rs similarity index 74% rename from clock-tui/src/bricks_text.rs rename to clock-tui/src/clock_text.rs index 9a89b53..2b786db 100644 --- a/clock-tui/src/bricks_text.rs +++ b/clock-tui/src/clock_text.rs @@ -1,28 +1,31 @@ use tui::{style::Style, widgets::Widget}; -use self::chars::{BrickChar, Point}; +use self::font::bricks::Bricks; +use self::font::Font; +use self::point::Point; -mod chars; +mod font; +mod point; pub struct BricksText { text: String, - size: u16, space: u16, style: Style, + font: Bricks, } impl BricksText { pub fn new(text: &str, size: u16, space: u16, style: Style) -> BricksText { BricksText { text: text.to_string(), - size, space, style, + font: Bricks { size }, } } pub fn size(&self) -> (u16, u16) { - let Point(w, h) = BrickChar::size(self.size); + let Point(w, h) = self.font.size(); let n_chars = self.text.chars().count() as u16; (w * n_chars + self.space * (n_chars - 1), h) } @@ -32,9 +35,8 @@ impl Widget for &BricksText { fn render(self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) { let mut area = area; for char in self.text.chars() { - let Point(w, _) = BrickChar::size(self.size); - let char = BrickChar::from(char); - char.render(self.size, self.style, area, buf); + let Point(w, _) = self.font.size(); + self.font.render(char, self.style, area, buf); let l = w + self.space; area.x += l; area.width = area.width.saturating_sub(l); diff --git a/clock-tui/src/clock_text/font.rs b/clock-tui/src/clock_text/font.rs new file mode 100644 index 0000000..9891b09 --- /dev/null +++ b/clock-tui/src/clock_text/font.rs @@ -0,0 +1,10 @@ +use tui::{buffer::Buffer, layout::Rect, style::Style}; + +use super::point::Point; + +pub mod bricks; + +pub(crate) trait Font { + fn size(&self) -> Point; + fn render(&self, char: char, style: Style, area: Rect, buf: &mut Buffer); +} diff --git a/clock-tui/src/clock_text/font/bricks.rs b/clock-tui/src/clock_text/font/bricks.rs new file mode 100644 index 0000000..b8c8d8c --- /dev/null +++ b/clock-tui/src/clock_text/font/bricks.rs @@ -0,0 +1,121 @@ +use std::cmp::min; + +use tui::{buffer::Buffer, layout::Rect, style::Style}; + +use super::Font; +use crate::clock_text::point::Point; + +pub struct Bricks { + pub size: u16, +} + +impl Bricks { + const UNIT_SIZE: Point = Point(6, 5); + + /// each row is represented with a vector of numbers: + /// the odd indexed items represent the lenght of "off", + /// the even indexed items represent the lenght of "on". + /// For exmaple: + /// vec![0, 6] is "██████" + /// vec![2, 2] is " ██" + /// vec![0, 2, 2, 2] is "██ ██" + fn draw_row( + start: Point, + row: Vec, + size: u16, + style: Style, + area: &Rect, + buf: &mut Buffer, + ) { + let mut p = start; + let mut on = false; + for len in row { + let len = len * size; + if p.0 > area.right() { + break; + } + + if on { + let s = min(len, area.right() - p.0 + 1); + let line = "█".repeat(s as usize); + for r in 0..size { + if p.1 > area.bottom() { + break; + } + buf.set_string(p.0, p.1 + r, line.as_str(), style); + } + } + + p.0 += len; + on = !on; + } + } + + fn draw_matrix(mat: [Vec; 5], size: u16, style: Style, area: &Rect, buf: &mut Buffer) { + let mut start = Point(area.x, area.y); + for row in mat { + Self::draw_row(start, row, size, style, area, buf); + start.1 += size; + } + } +} + +impl Font for Bricks { + fn size(&self) -> Point { + Self::UNIT_SIZE * self.size + } + + fn render(&self, char: char, style: Style, area: Rect, buf: &mut Buffer) { + let size = self.size; + let mut render_matrix = |mat: [Vec; 5]| { + Bricks::draw_matrix(mat, size, style, &area, buf); + }; + + match char { + '0' => render_matrix([ + vec![0, 6], + vec![0, 2, 2, 2], + vec![0, 2, 2, 2], + vec![0, 2, 2, 2], + vec![0, 6], + ]), + '1' => render_matrix([vec![0, 4], vec![2, 2], vec![2, 2], vec![2, 2], vec![0, 6]]), + '2' => render_matrix([vec![0, 6], vec![4, 2], vec![0, 6], vec![0, 2], vec![0, 6]]), + '3' => render_matrix([vec![0, 6], vec![4, 2], vec![0, 6], vec![4, 2], vec![0, 6]]), + '4' => render_matrix([ + vec![0, 2, 2, 2], + vec![0, 2, 2, 2], + vec![0, 6], + vec![4, 2], + vec![4, 2], + ]), + '5' => render_matrix([vec![0, 6], vec![0, 2], vec![0, 6], vec![4, 2], vec![0, 6]]), + '6' => render_matrix([ + vec![0, 6], + vec![0, 2], + vec![0, 6], + vec![0, 2, 2, 2], + vec![0, 6], + ]), + '7' => render_matrix([vec![0, 6], vec![4, 2], vec![4, 2], vec![4, 2], vec![4, 2]]), + '8' => render_matrix([ + vec![0, 6], + vec![0, 2, 2, 2], + vec![0, 6], + vec![0, 2, 2, 2], + vec![0, 6], + ]), + '9' => render_matrix([ + vec![0, 6], + vec![0, 2, 2, 2], + vec![0, 6], + vec![4, 2], + vec![0, 6], + ]), + ':' => render_matrix([vec![], vec![2, 2], vec![], vec![2, 2], vec![]]), + '.' => render_matrix([vec![], vec![], vec![], vec![], vec![2, 2]]), + '-' => render_matrix([vec![], vec![], vec![0, 6], vec![], vec![]]), + _ => {} + } + } +} diff --git a/clock-tui/src/clock_text/point.rs b/clock-tui/src/clock_text/point.rs new file mode 100644 index 0000000..fe351d0 --- /dev/null +++ b/clock-tui/src/clock_text/point.rs @@ -0,0 +1,20 @@ +use std::ops; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Point(pub u16, pub u16); + +impl ops::Add<&Point> for Point { + type Output = Point; + + fn add(self, other: &Point) -> Point { + Point(self.0 + other.0, self.1 + other.1) + } +} + +impl ops::Mul for Point { + type Output = Point; + + fn mul(self, other: u16) -> Point { + Point(self.0 * other, self.1 * other) + } +} diff --git a/clock-tui/src/lib.rs b/clock-tui/src/lib.rs index 7f0012d..e1cdb8b 100644 --- a/clock-tui/src/lib.rs +++ b/clock-tui/src/lib.rs @@ -1,2 +1,2 @@ pub mod app; -pub mod bricks_text; +pub mod clock_text; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 01284f2..26f95c3 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,10 +1,10 @@ -use clap::{IntoApp, ArgEnum}; -use clap_complete::{Shell, generate_to}; +use clap::{ArgEnum, IntoApp}; +use clap_complete::{generate_to, Shell}; use clap_mangen::Man; use clock_tui::app::App; use std::fs::File; use std::io::Result; -use std::path::{PathBuf, Path}; +use std::path::{Path, PathBuf}; use std::{env, fs}; const BIN_NAME: &str = "tclock";