mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-31 01:24:31 +02:00
update grids
This commit is contained in:
parent
c68b190388
commit
33692afb14
@ -150,21 +150,39 @@ trait Grid: Debug {
|
|||||||
fn reset(&mut self);
|
fn reset(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// The `BrailleGrid` is a grid made up of cells each containing a Braille pattern.
|
||||||
|
///
|
||||||
|
/// This makes it possible to draw shapes with a resolution of 2x4 dots per cell. This is useful
|
||||||
|
/// when you want to draw shapes with a high resolution. Font support for Braille patterns is
|
||||||
|
/// required to see the dots. If your terminal or font does not support this unicode block, you
|
||||||
|
/// will see unicode replacement characters (<28>) instead of braille dots.
|
||||||
|
///
|
||||||
|
/// This grid type only supports a single foreground color for each 2x4 dots cell. There is no way
|
||||||
|
/// to set the individual color of each dot in the braille pattern.
|
||||||
|
#[derive(Debug)]
|
||||||
struct BrailleGrid {
|
struct BrailleGrid {
|
||||||
|
/// Width of the grid in number of terminal columns
|
||||||
width: u16,
|
width: u16,
|
||||||
|
/// Height of the grid in number of terminal rows
|
||||||
height: u16,
|
height: u16,
|
||||||
cells: Vec<u16>, // FIXME: (points_rework_v1) isn't this really inefficient to go u16 -> String from utf16?
|
/// Represents the unicode braille patterns. Will take a value between `0x2800` and `0x28FF`
|
||||||
|
/// this is converted to an utf16 string when converting to a layer. See
|
||||||
|
/// <https://en.wikipedia.org/wiki/Braille_Patterns> for more info.
|
||||||
|
utf16_code_points: Vec<u16>,
|
||||||
|
/// The color of each cell only supports foreground colors for now as there's no way to
|
||||||
|
/// individually set the background color of each dot in the braille pattern.
|
||||||
colors: Vec<Color>,
|
colors: Vec<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrailleGrid {
|
impl BrailleGrid {
|
||||||
fn new(width: u16, height: u16) -> BrailleGrid {
|
/// Create a new `BrailleGrid` with the given width and height measured in terminal columns and
|
||||||
|
/// rows respectively.
|
||||||
|
fn new(width: u16, height: u16) -> Self {
|
||||||
let length = usize::from(width * height);
|
let length = usize::from(width * height);
|
||||||
BrailleGrid {
|
Self {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
cells: vec![symbols::braille::BLANK; length],
|
utf16_code_points: vec![symbols::braille::BLANK; length],
|
||||||
colors: vec![Color::Reset; length],
|
colors: vec![Color::Reset; length],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,58 +190,58 @@ impl BrailleGrid {
|
|||||||
|
|
||||||
impl Grid for BrailleGrid {
|
impl Grid for BrailleGrid {
|
||||||
fn resolution(&self) -> (f64, f64) {
|
fn resolution(&self) -> (f64, f64) {
|
||||||
(
|
(f64::from(self.width) * 2.0, f64::from(self.height) * 4.0)
|
||||||
f64::from(self.width) * 2.0 - 1.0,
|
|
||||||
f64::from(self.height) * 4.0 - 1.0,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) -> Layer {
|
fn save(&self) -> Layer {
|
||||||
Layer {
|
let string = String::from_utf16(&self.utf16_code_points).unwrap();
|
||||||
string: String::from_utf16(&self.cells).unwrap(),
|
// the background color is always reset for braille patterns
|
||||||
colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
|
let colors = self.colors.iter().map(|c| (*c, Color::Reset)).collect();
|
||||||
}
|
Layer { string, colors }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
for c in &mut self.cells {
|
self.utf16_code_points.fill(symbols::braille::BLANK);
|
||||||
*c = symbols::braille::BLANK;
|
self.colors.fill(Color::Reset);
|
||||||
}
|
|
||||||
for c in &mut self.colors {
|
|
||||||
*c = Color::Reset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, x: usize, y: usize, color: Color) {
|
fn paint(&mut self, x: usize, y: usize, color: Color) {
|
||||||
let index = y / 4 * self.width as usize + x / 2;
|
let index = y / 4 * self.width as usize + x / 2;
|
||||||
if let Some(curr_color) = self.colors.get_mut(index) {
|
// using get_mut here because we are indexing the vector with usize values
|
||||||
if *curr_color != color {
|
// and we want to make sure we don't panic if the index is out of bounds
|
||||||
*curr_color = color;
|
if let Some(c) = self.utf16_code_points.get_mut(index) {
|
||||||
if let Some(cell) = self.cells.get_mut(index) {
|
*c |= symbols::braille::DOTS[y % 4][x % 2];
|
||||||
*cell = symbols::braille::BLANK;
|
}
|
||||||
|
if let Some(c) = self.colors.get_mut(index) {
|
||||||
*cell |= symbols::braille::DOTS[y % 4][x % 2];
|
*c = color;
|
||||||
}
|
|
||||||
} else if let Some(c) = self.cells.get_mut(index) {
|
|
||||||
*c |= symbols::braille::DOTS[y % 4][x % 2];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// The `CharGrid` is a grid made up of cells each containing a single character.
|
||||||
|
///
|
||||||
|
/// This makes it possible to draw shapes with a resolution of 1x1 dots per cell. This is useful
|
||||||
|
/// when you want to draw shapes with a low resolution.
|
||||||
|
#[derive(Debug)]
|
||||||
struct CharGrid {
|
struct CharGrid {
|
||||||
|
/// Width of the grid in number of terminal columns
|
||||||
width: u16,
|
width: u16,
|
||||||
|
/// Height of the grid in number of terminal rows
|
||||||
height: u16,
|
height: u16,
|
||||||
|
/// Represents a single character for each cell
|
||||||
cells: Vec<char>,
|
cells: Vec<char>,
|
||||||
|
/// The color of each cell
|
||||||
colors: Vec<Color>,
|
colors: Vec<Color>,
|
||||||
|
/// The character to use for every cell - e.g. a block, dot, etc.
|
||||||
cell_char: char,
|
cell_char: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharGrid {
|
impl CharGrid {
|
||||||
fn new(width: u16, height: u16, cell_char: char) -> CharGrid {
|
/// Create a new `CharGrid` with the given width and height measured in terminal columns and
|
||||||
|
/// rows respectively.
|
||||||
|
fn new(width: u16, height: u16, cell_char: char) -> Self {
|
||||||
let length = usize::from(width * height);
|
let length = usize::from(width * height);
|
||||||
CharGrid {
|
Self {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
cells: vec![' '; length],
|
cells: vec![' '; length],
|
||||||
@ -235,7 +253,7 @@ impl CharGrid {
|
|||||||
|
|
||||||
impl Grid for CharGrid {
|
impl Grid for CharGrid {
|
||||||
fn resolution(&self) -> (f64, f64) {
|
fn resolution(&self) -> (f64, f64) {
|
||||||
(f64::from(self.width) - 1.0, f64::from(self.height) - 1.0)
|
(f64::from(self.width), f64::from(self.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) -> Layer {
|
fn save(&self) -> Layer {
|
||||||
@ -246,16 +264,14 @@ impl Grid for CharGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
for c in &mut self.cells {
|
self.cells.fill(' ');
|
||||||
*c = ' ';
|
self.colors.fill(Color::Reset);
|
||||||
}
|
|
||||||
for c in &mut self.colors {
|
|
||||||
*c = Color::Reset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, x: usize, y: usize, color: Color) {
|
fn paint(&mut self, x: usize, y: usize, color: Color) {
|
||||||
let index = y * self.width as usize + x;
|
let index = y * self.width as usize + x;
|
||||||
|
// using get_mut here because we are indexing the vector with usize values
|
||||||
|
// and we want to make sure we don't panic if the index is out of bounds
|
||||||
if let Some(c) = self.cells.get_mut(index) {
|
if let Some(c) = self.cells.get_mut(index) {
|
||||||
*c = self.cell_char;
|
*c = self.cell_char;
|
||||||
}
|
}
|
||||||
@ -265,42 +281,34 @@ impl Grid for CharGrid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `HalfBlockGrid` is a grid made up of cells each containing a half block character.
|
||||||
|
///
|
||||||
|
/// In terminals, each character is usually twice as tall as it is wide. Unicode has a couple of
|
||||||
|
/// vertical half block characters, the upper half block '▀' and lower half block '▄' which take up
|
||||||
|
/// half the height of a normal character but the full width. Together with an empty space ' ' and a
|
||||||
|
/// full block '█', we can effectively double the resolution of a single cell. In addition, because
|
||||||
|
/// each character can have a foreground and background color, we can control the color of the upper
|
||||||
|
/// and lower half of each cell. This allows us to draw shapes with a resolution of 1x2 "pixels" per
|
||||||
|
/// cell.
|
||||||
|
///
|
||||||
|
/// This allows for more flexibility than the `BrailleGrid` which only supports a single
|
||||||
|
/// foreground color for each 2x4 dots cell, and the `CharGrid` which only supports a single
|
||||||
|
/// character for each cell.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Painter<'a, 'b> {
|
|
||||||
context: &'a mut Context<'b>,
|
|
||||||
resolution: (f64, f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The HalfBlockGrid is a grid made up of cells each containing a half block
|
|
||||||
/// character.
|
|
||||||
///
|
|
||||||
/// In terminals, each character is usually twice as tall as it is wide. Unicode
|
|
||||||
/// has a couple of vertical half block characters, the upper half block '▀' and
|
|
||||||
/// lower half block '▄' which take up half the height of a normal character but
|
|
||||||
/// the full width. Together with an empty space ' ' and a full block '█', we
|
|
||||||
/// can effectively double the resolution of a single cell. In addition, because
|
|
||||||
/// each character can have a foreground and background color, we can control
|
|
||||||
/// the color of the upper and lower half of each cell. This allows us to draw
|
|
||||||
/// shapes with a resolution of 1x2 "pixels" per cell.
|
|
||||||
///
|
|
||||||
/// This allows for more flexibility than the BrailleGrid which only supports a
|
|
||||||
/// single foreground color for each 2x4 dots cell, and the CharGrid which only
|
|
||||||
/// supports a single character for each cell.
|
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
|
||||||
struct HalfBlockGrid {
|
struct HalfBlockGrid {
|
||||||
/// width of the grid in number of terminal columns
|
/// Width of the grid in number of terminal columns
|
||||||
width: u16,
|
width: u16,
|
||||||
/// height of the grid in number of terminal rows
|
/// Height of the grid in number of terminal rows
|
||||||
height: u16,
|
height: u16,
|
||||||
/// represents a single color for each "pixel" arranged in column, row order
|
/// Represents a single color for each "pixel" arranged in column, row order
|
||||||
pixels: Vec<Vec<Color>>,
|
pixels: Vec<Vec<Color>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HalfBlockGrid {
|
impl HalfBlockGrid {
|
||||||
/// Create a new [`HalfBlockGrid`] with the given width and height measured
|
/// Create a new `HalfBlockGrid` with the given width and height measured in terminal columns
|
||||||
/// in terminal columns and rows respectively.
|
/// and rows respectively.
|
||||||
fn new(width: u16, height: u16) -> HalfBlockGrid {
|
fn new(width: u16, height: u16) -> Self {
|
||||||
HalfBlockGrid {
|
Self {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
|
pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
|
||||||
@ -314,11 +322,10 @@ impl Grid for HalfBlockGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) -> Layer {
|
fn save(&self) -> Layer {
|
||||||
// Given that we store the pixels in a grid, and that we want to use 2 pixels
|
// Given that we store the pixels in a grid, and that we want to use 2 pixels arranged
|
||||||
// arranged vertically to form a single terminal cell, which can be
|
// vertically to form a single terminal cell, which can be either empty, upper half block,
|
||||||
// either empty, upper half block, lower half block or full block, we
|
// lower half block or full block, we need examine the pixels in vertical pairs to decide
|
||||||
// need examine the pixels in vertical pairs to decide what character to
|
// what character to print in each cell. So these are the 4 states we use to represent each
|
||||||
// print in each cell. So these are the 4 states we use to represent each
|
|
||||||
// cell:
|
// cell:
|
||||||
//
|
//
|
||||||
// 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
|
// 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
|
||||||
@ -326,44 +333,51 @@ impl Grid for HalfBlockGrid {
|
|||||||
// 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
|
// 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
|
||||||
// 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
|
// 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
|
||||||
//
|
//
|
||||||
// Note that because the foreground reset color (i.e. default foreground color)
|
// Note that because the foreground reset color (i.e. default foreground color) is usually
|
||||||
// is usually not the same as the background reset color (i.e. default
|
// not the same as the background reset color (i.e. default background color), we need to
|
||||||
// background color), we need to swap around the colors for that state
|
// swap around the colors for that state (2 reset/color).
|
||||||
// (2 reset/color).
|
|
||||||
//
|
//
|
||||||
// When the upper and lower colors are the same, we could continue to use an
|
// When the upper and lower colors are the same, we could continue to use an upper half
|
||||||
// upper half block, but we choose to use a full block instead. This
|
// block, but we choose to use a full block instead. This allows us to write unit tests that
|
||||||
// allows us to write unit tests that treat the cell as a single
|
// treat the cell as a single character instead of two half block characters.
|
||||||
// character instead of two half block characters.
|
|
||||||
|
|
||||||
// Note we implement this slightly differently to what is done in ratatui's
|
// first we join each adjacent row together to get an iterator that contains vertical pairs
|
||||||
// repo, since their version doesn't seem to compile for me...
|
// of pixels, with the lower row being the first element in the pair
|
||||||
//
|
let vertical_color_pairs = self
|
||||||
// TODO: Whenever I add this as a valid marker, make sure this works fine with
|
|
||||||
// the overridden time_chart drawing-layer-thing.
|
|
||||||
|
|
||||||
// Join the upper and lower rows, and emit a tuple vector of strings to print,
|
|
||||||
// and their colours.
|
|
||||||
let (string, colors) = self
|
|
||||||
.pixels
|
.pixels
|
||||||
.iter()
|
.iter()
|
||||||
.tuples()
|
.tuples()
|
||||||
.flat_map(|(upper_row, lower_row)| zip(upper_row, lower_row))
|
.flat_map(|(upper_row, lower_row)| zip(upper_row, lower_row));
|
||||||
|
|
||||||
|
// then we work out what character to print for each pair of pixels
|
||||||
|
let string = vertical_color_pairs
|
||||||
|
.clone()
|
||||||
.map(|(upper, lower)| match (upper, lower) {
|
.map(|(upper, lower)| match (upper, lower) {
|
||||||
(Color::Reset, Color::Reset) => (' ', (Color::Reset, Color::Reset)),
|
(Color::Reset, Color::Reset) => ' ',
|
||||||
(Color::Reset, &lower) => (symbols::half_block::LOWER, (Color::Reset, lower)),
|
(Color::Reset, _) => symbols::half_block::LOWER,
|
||||||
(&upper, Color::Reset) => (symbols::half_block::UPPER, (upper, Color::Reset)),
|
(_, Color::Reset) => symbols::half_block::UPPER,
|
||||||
(&upper, &lower) => {
|
(&lower, &upper) => {
|
||||||
let c = if lower == upper {
|
if lower == upper {
|
||||||
symbols::half_block::FULL
|
symbols::half_block::FULL
|
||||||
} else {
|
} else {
|
||||||
symbols::half_block::UPPER
|
symbols::half_block::UPPER
|
||||||
};
|
}
|
||||||
|
|
||||||
(c, (upper, lower))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unzip();
|
.collect();
|
||||||
|
|
||||||
|
// then we convert these each vertical pair of pixels into a foreground and background color
|
||||||
|
let colors = vertical_color_pairs
|
||||||
|
.map(|(upper, lower)| {
|
||||||
|
let (fg, bg) = match (upper, lower) {
|
||||||
|
(Color::Reset, Color::Reset) => (Color::Reset, Color::Reset),
|
||||||
|
(Color::Reset, &lower) => (lower, Color::Reset),
|
||||||
|
(&upper, Color::Reset) => (upper, Color::Reset),
|
||||||
|
(&upper, &lower) => (upper, lower),
|
||||||
|
};
|
||||||
|
(fg, bg)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Layer { string, colors }
|
Layer { string, colors }
|
||||||
}
|
}
|
||||||
@ -377,6 +391,12 @@ impl Grid for HalfBlockGrid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Painter<'a, 'b> {
|
||||||
|
context: &'a mut Context<'b>,
|
||||||
|
resolution: (f64, f64),
|
||||||
|
}
|
||||||
|
|
||||||
impl Painter<'_, '_> {
|
impl Painter<'_, '_> {
|
||||||
/// Convert the (x, y) coordinates to location of a point on the grid.
|
/// Convert the (x, y) coordinates to location of a point on the grid.
|
||||||
pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
|
pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user