diff --git a/docs/content/troubleshooting.md b/docs/content/troubleshooting.md
index 192baed2..4e178ca5 100644
--- a/docs/content/troubleshooting.md
+++ b/docs/content/troubleshooting.md
@@ -2,15 +2,19 @@
## The graph points look broken/strange
-It's possible that your graphs won't look great out of the box due to the reliance on braille fonts to draw them. One
-example of this is seeing a bunch of missing font characters, caused when the terminal isn't configured properly to
-render braille fonts.
+It's possible that your graphs don't look great out of the box due to the reliance on
+[braille characters](https://en.wikipedia.org/wiki/Braille_Patterns) to draw them. This could cause problems if
+your terminal's font does not support them, or your terminal is not configured properly to draw them.
An example of missing braille fonts in Powershell
+Some possible solutions are included below.
+
+### Use dot markers instead
+
One alternative is to use the `--dot_marker` option to render graph charts using dots instead of the braille characters,
which generally seems better supported out of the box, at the expense of looking less intricate:
@@ -19,28 +23,29 @@ which generally seems better supported out of the box, at the expense of looking
Example using btm --dot_marker
-Another (better) alternative is to install a font that supports braille fonts, and configure your terminal emulator to use it.
-For example, installing something like [UBraille](https://yudit.org/download/fonts/UBraille/) or [Iosevka](https://github.com/be5invis/Iosevka)
-and ensuring your terminal uses it should work.
+### Use a font that supports braille fonts
-### Linux/macOS/Unix
+Another (better) alternative is to install a font that supports braille fonts, and configure your terminal emulator to
+use it. For example, installing something like [UBraille](https://yudit.org/download/fonts/UBraille/) or
+[Iosevka](https://github.com/be5invis/Iosevka) and ensuring your terminal uses it should work.
-If you're on a Unix-like system, generally, the problem comes down to you either not having a font that supports the
-braille markers, or your terminal emulator is not using the correct font for the braille markers.
+#### Linux/macOS/Unix
-Some possible solutions include:
+Solutions mostly depend on what terminal emulator you are using, so unfortunately, I can't give specific instructions.
+Here are some possible solutions:
- Uninstalling `gnu-free-fonts` if installed, as that is known to cause problems with braille markers
- Installing a font like `ttf-symbola` or `ttf-ubraille` for your terminal emulator to try and automatically fall back to
- Configuring your terminal emulator to use specific fonts for the `U+2800` to `U+28FF` range.
- For example for kitty, do `symbol_map U+2800-U+28FF Symbola`.
-See [this issue](https://github.com/cjbassi/gotop/issues/18) for more possible fixes.
+For some more possible solutions:
-If you're still having issues, feel free to open a [discussion](https://github.com/ClementTsang/bottom/discussions/new/)
-question about it.
+- Check out [this issue](https://github.com/cjbassi/gotop/issues/18) from gotop about the same issue.
+- See ratatui's [FAQ](https://ratatui.rs/faq/#some-characters-appear-to-be-missing--look-weird) (ratatui is the underlying
+ library bottom uses to draw things).
-### Windows/Powershell
+#### Windows and Powershell
**Note: I would advise backing up your registry beforehand if you aren't sure what you are doing!**
@@ -73,6 +78,11 @@ Let's say you're installing [Iosevka](https://github.com/be5invis/Iosevka). The
+### Still having issues?
+
+If you're still having issues, feel free to open a [discussion](https://github.com/ClementTsang/bottom/discussions/new/)
+question about it, and I (or others) can try to help.
+
## Why can't I see all my temperature sensors on Windows?
This is a [known limitation](./support/official.md#windows), some sensors may require admin privileges to get sensor data.
diff --git a/src/canvas/components/time_graph/time_chart.rs b/src/canvas/components/time_graph/time_chart.rs
index d7bcc99f..127b8239 100644
--- a/src/canvas/components/time_graph/time_chart.rs
+++ b/src/canvas/components/time_graph/time_chart.rs
@@ -5,6 +5,7 @@
//! the specializations are factored out to `time_chart/points.rs`.
mod canvas;
+mod grid;
mod points;
use std::{cmp::max, str::FromStr, time::Instant};
@@ -302,7 +303,7 @@ impl<'a> Dataset<'a> {
/// Sets the data points of this dataset
///
- /// Points will then either be rendered as scrattered points or with lines
+ /// Points will then either be rendered as scattered points or with lines
/// between them depending on [`Dataset::graph_type`].
///
/// Data consist in an array of `f64` tuples (`(f64, f64)`), the first
@@ -1111,7 +1112,7 @@ mod tests {
}
#[test]
- fn datasets_without_name_dont_contribute_to_legend_height() {
+ fn datasets_without_name_do_not_contribute_to_legend_height() {
let data_named_1 = Dataset::default().name("data1"); // must occupy a row in legend
let data_named_2 = Dataset::default().name(""); // must occupy a row in legend, even if name is empty
let data_unnamed = Dataset::default(); // must not occupy a row in legend
diff --git a/src/canvas/components/time_graph/time_chart/canvas.rs b/src/canvas/components/time_graph/time_chart/canvas.rs
index 910aad0e..4378bba6 100644
--- a/src/canvas/components/time_graph/time_chart/canvas.rs
+++ b/src/canvas/components/time_graph/time_chart/canvas.rs
@@ -12,9 +12,6 @@
//! See and for the
//! original motivation.
-use std::{fmt::Debug, iter::zip};
-
-use itertools::Itertools;
use tui::{
buffer::Buffer,
layout::Rect,
@@ -27,6 +24,8 @@ use tui::{
},
};
+use super::grid::{BrailleGrid, CharGrid, Grid, HalfBlockGrid};
+
/// Interface for all shapes that may be drawn on a Canvas widget.
pub trait Shape {
fn draw(&self, painter: &mut Painter<'_, '_>);
@@ -135,265 +134,27 @@ pub struct Label<'a> {
spans: Line<'a>,
}
-#[derive(Debug, Clone)]
-struct Layer {
- string: String,
- colors: Vec<(Color, Color)>,
-}
-
-trait Grid: Debug {
- // fn width(&self) -> u16;
- // fn height(&self) -> u16;
- fn resolution(&self) -> (f64, f64);
- fn paint(&mut self, x: usize, y: usize, color: Color);
- fn save(&self) -> Layer;
- fn reset(&mut self);
-}
-
-#[derive(Debug, Clone)]
-struct BrailleGrid {
- width: u16,
- height: u16,
- cells: Vec, // FIXME: (points_rework_v1) isn't this really inefficient to go u16 -> String from utf16?
- colors: Vec,
-}
-
-impl BrailleGrid {
- fn new(width: u16, height: u16) -> BrailleGrid {
- let length = usize::from(width * height);
- BrailleGrid {
- width,
- height,
- cells: vec![symbols::braille::BLANK; length],
- colors: vec![Color::Reset; length],
- }
- }
-}
-
-impl Grid for BrailleGrid {
- fn resolution(&self) -> (f64, f64) {
- (
- f64::from(self.width) * 2.0 - 1.0,
- f64::from(self.height) * 4.0 - 1.0,
- )
- }
-
- fn save(&self) -> Layer {
- Layer {
- string: String::from_utf16(&self.cells).unwrap(),
- colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
- }
- }
-
- fn reset(&mut self) {
- for c in &mut self.cells {
- *c = symbols::braille::BLANK;
- }
- for c in &mut self.colors {
- *c = Color::Reset;
- }
- }
-
- fn paint(&mut self, x: usize, y: usize, color: Color) {
- let index = y / 4 * self.width as usize + x / 2;
- if let Some(curr_color) = self.colors.get_mut(index) {
- if *curr_color != color {
- *curr_color = color;
- if let Some(cell) = self.cells.get_mut(index) {
- *cell = symbols::braille::BLANK;
-
- *cell |= symbols::braille::DOTS[y % 4][x % 2];
- }
- } else if let Some(c) = self.cells.get_mut(index) {
- *c |= symbols::braille::DOTS[y % 4][x % 2];
- }
- }
- }
-}
-
-#[derive(Debug, Clone)]
-struct CharGrid {
- width: u16,
- height: u16,
- cells: Vec,
- colors: Vec,
- cell_char: char,
-}
-
-impl CharGrid {
- fn new(width: u16, height: u16, cell_char: char) -> CharGrid {
- let length = usize::from(width * height);
- CharGrid {
- width,
- height,
- cells: vec![' '; length],
- colors: vec![Color::Reset; length],
- cell_char,
- }
- }
-}
-
-impl Grid for CharGrid {
- fn resolution(&self) -> (f64, f64) {
- (f64::from(self.width) - 1.0, f64::from(self.height) - 1.0)
- }
-
- fn save(&self) -> Layer {
- Layer {
- string: self.cells.iter().collect(),
- colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
- }
- }
-
- fn reset(&mut self) {
- for c in &mut self.cells {
- *c = ' ';
- }
- for c in &mut self.colors {
- *c = Color::Reset;
- }
- }
-
- fn paint(&mut self, x: usize, y: usize, color: Color) {
- let index = y * self.width as usize + x;
- if let Some(c) = self.cells.get_mut(index) {
- *c = self.cell_char;
- }
- if let Some(c) = self.colors.get_mut(index) {
- *c = color;
- }
- }
-}
-
#[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 {
- /// width of the grid in number of terminal columns
- width: u16,
- /// height of the grid in number of terminal rows
- height: u16,
- /// represents a single color for each "pixel" arranged in column, row order
- pixels: Vec>,
-}
-
-impl HalfBlockGrid {
- /// Create a new [`HalfBlockGrid`] with the given width and height measured
- /// in terminal columns and rows respectively.
- fn new(width: u16, height: u16) -> HalfBlockGrid {
- HalfBlockGrid {
- width,
- height,
- pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
- }
- }
-}
-
-impl Grid for HalfBlockGrid {
- fn resolution(&self) -> (f64, f64) {
- (f64::from(self.width), f64::from(self.height) * 2.0)
- }
-
- fn save(&self) -> Layer {
- // Given that we store the pixels in a grid, and that we want to use 2 pixels
- // arranged vertically to form a single terminal cell, which can be
- // either empty, upper half block, lower half block or full block, we
- // need examine the pixels in vertical pairs to decide what character to
- // print in each cell. So these are the 4 states we use to represent each
- // cell:
- //
- // 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
- // 2. upper: reset, lower: color => '▄' fg: lower color / bg: reset
- // 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
- // 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
- //
- // Note that because the foreground reset color (i.e. default foreground color)
- // is usually not the same as the background reset color (i.e. default
- // background color), we need to swap around the colors for that state
- // (2 reset/color).
- //
- // When the upper and lower colors are the same, we could continue to use an
- // upper half block, but we choose to use a full block instead. This
- // allows us to write unit tests that treat the cell as a single
- // character instead of two half block characters.
-
- // Note we implement this slightly differently to what is done in ratatui's
- // repo, since their version doesn't seem to compile for me...
- //
- // 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
- .iter()
- .tuples()
- .flat_map(|(upper_row, lower_row)| zip(upper_row, lower_row))
- .map(|(upper, lower)| match (upper, lower) {
- (Color::Reset, Color::Reset) => (' ', (Color::Reset, Color::Reset)),
- (Color::Reset, &lower) => (symbols::half_block::LOWER, (Color::Reset, lower)),
- (&upper, Color::Reset) => (symbols::half_block::UPPER, (upper, Color::Reset)),
- (&upper, &lower) => {
- let c = if lower == upper {
- symbols::half_block::FULL
- } else {
- symbols::half_block::UPPER
- };
-
- (c, (upper, lower))
- }
- })
- .unzip();
-
- Layer { string, colors }
- }
-
- fn reset(&mut self) {
- self.pixels.fill(vec![Color::Reset; self.width as usize]);
- }
-
- fn paint(&mut self, x: usize, y: usize, color: Color) {
- self.pixels[y][x] = color;
- }
-}
-
impl Painter<'_, '_> {
/// 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)> {
- let left = self.context.x_bounds[0];
- let right = self.context.x_bounds[1];
- let top = self.context.y_bounds[1];
- let bottom = self.context.y_bounds[0];
+ let [left, right] = self.context.x_bounds;
+ let [bottom, top] = self.context.y_bounds;
if x < left || x > right || y < bottom || y > top {
return None;
}
- let width = (self.context.x_bounds[1] - self.context.x_bounds[0]).abs();
- let height = (self.context.y_bounds[1] - self.context.y_bounds[0]).abs();
- if width == 0.0 || height == 0.0 {
+ let width = right - left;
+ let height = top - bottom;
+ if width <= 0.0 || height <= 0.0 {
return None;
}
- let x = ((x - left) * self.resolution.0 / width) as usize;
- let y = ((top - y) * self.resolution.1 / height) as usize;
+ let x = ((x - left) * (self.resolution.0 - 1.0) / width).round() as usize;
+ let y = ((top - y) * (self.resolution.1 - 1.0) / height).round() as usize;
Some((x, y))
}
diff --git a/src/canvas/components/time_graph/time_chart/grid.rs b/src/canvas/components/time_graph/time_chart/grid.rs
new file mode 100644
index 00000000..73aadb52
--- /dev/null
+++ b/src/canvas/components/time_graph/time_chart/grid.rs
@@ -0,0 +1,296 @@
+use std::{fmt::Debug, iter::zip};
+
+use itertools::Itertools;
+use tui::{style::Color, symbols};
+
+#[derive(Debug, Clone)]
+pub(super) struct Layer {
+ pub(super) string: String,
+ pub(super) colors: Vec<(Color, Color)>,
+}
+
+/// A [`Grid`] is a trait that represents a grid of cells, drawn in a
+/// specific way.
+pub(super) trait Grid: Debug {
+ /// Get the resolution of the grid in number of dots.
+ ///
+ /// This doesn't have to be the same as the number of rows and columns of the grid. For example,
+ /// a grid of Braille patterns will have a resolution of 2x4 dots per cell. This means that a
+ /// grid of 10x10 cells will have a resolution of 20x40 dots.
+ fn resolution(&self) -> (f64, f64);
+ /// Paint a point of the grid.
+ ///
+ /// The point is expressed in number of dots starting at the origin of the grid in the top left
+ /// corner. Note that this is not the same as the `(x, y)` coordinates of the canvas.
+ fn paint(&mut self, x: usize, y: usize, color: Color);
+ /// Save the current state of the [`Grid`] as a layer to be rendered
+ fn save(&self) -> Layer;
+ /// Reset the grid to its initial state
+ fn reset(&mut self);
+}
+
+/// 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 (�) 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)]
+pub(super) struct BrailleGrid {
+ /// Width of the grid in number of terminal columns
+ width: u16,
+ /// Height of the grid in number of terminal rows
+ height: u16,
+ /// 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
+ /// for more info.
+ ///
+ /// FIXME: (points_rework_v1) isn't this really inefficient to go u16 -> String from utf16?
+ utf16_code_points: Vec,
+ /// 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,
+}
+
+impl BrailleGrid {
+ /// Create a new `BrailleGrid` with the given width and height measured in terminal columns and
+ /// rows respectively.
+ pub(super) fn new(width: u16, height: u16) -> Self {
+ let length = usize::from(width * height);
+ Self {
+ width,
+ height,
+ utf16_code_points: vec![symbols::braille::BLANK; length],
+ colors: vec![Color::Reset; length],
+ }
+ }
+}
+
+impl Grid for BrailleGrid {
+ fn resolution(&self) -> (f64, f64) {
+ (f64::from(self.width) * 2.0, f64::from(self.height) * 4.0)
+ }
+
+ fn save(&self) -> Layer {
+ let string = String::from_utf16(&self.utf16_code_points).unwrap();
+ // the background color is always reset for braille patterns
+ let colors = self.colors.iter().map(|c| (*c, Color::Reset)).collect();
+ Layer { string, colors }
+ }
+
+ fn reset(&mut self) {
+ self.utf16_code_points.fill(symbols::braille::BLANK);
+ self.colors.fill(Color::Reset);
+ }
+
+ fn paint(&mut self, x: usize, y: usize, color: Color) {
+ // Note the braille array corresponds to:
+ // ⠁⠈
+ // ⠂⠐
+ // ⠄⠠
+ // ⡀⢀
+
+ let index = y / 4 * self.width as usize + x / 2;
+
+ // The ratatui/tui-rs implementation; this gives a more merged
+ // look but it also makes it a bit harder to read in some cases.
+
+ // if let Some(c) = self.utf16_code_points.get_mut(index) {
+ // *c |= symbols::braille::DOTS[y % 4][x % 2];
+ // }
+ // if let Some(c) = self.colors.get_mut(index) {
+ // *c = color;
+ // }
+
+ // Custom implementation to distinguish between lines better.
+ if let Some(curr_color) = self.colors.get_mut(index) {
+ if *curr_color != color {
+ *curr_color = color;
+ if let Some(cell) = self.utf16_code_points.get_mut(index) {
+ *cell = symbols::braille::BLANK | symbols::braille::DOTS[y % 4][x % 2];
+ }
+ } else if let Some(cell) = self.utf16_code_points.get_mut(index) {
+ *cell |= symbols::braille::DOTS[y % 4][x % 2];
+ }
+ }
+ }
+}
+
+/// 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)]
+pub(super) struct CharGrid {
+ /// Width of the grid in number of terminal columns
+ width: u16,
+ /// Height of the grid in number of terminal rows
+ height: u16,
+ /// Represents a single character for each cell
+ cells: Vec,
+ /// The color of each cell
+ colors: Vec,
+ /// The character to use for every cell - e.g. a block, dot, etc.
+ cell_char: char,
+}
+
+impl CharGrid {
+ /// Create a new `CharGrid` with the given width and height measured in terminal columns and
+ /// rows respectively.
+ pub(super) fn new(width: u16, height: u16, cell_char: char) -> Self {
+ let length = usize::from(width * height);
+ Self {
+ width,
+ height,
+ cells: vec![' '; length],
+ colors: vec![Color::Reset; length],
+ cell_char,
+ }
+ }
+}
+
+impl Grid for CharGrid {
+ fn resolution(&self) -> (f64, f64) {
+ (f64::from(self.width), f64::from(self.height))
+ }
+
+ fn save(&self) -> Layer {
+ Layer {
+ string: self.cells.iter().collect(),
+ colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
+ }
+ }
+
+ fn reset(&mut self) {
+ self.cells.fill(' ');
+ self.colors.fill(Color::Reset);
+ }
+
+ fn paint(&mut self, x: usize, y: usize, color: Color) {
+ 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) {
+ *c = self.cell_char;
+ }
+ if let Some(c) = self.colors.get_mut(index) {
+ *c = color;
+ }
+ }
+}
+
+/// 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)]
+pub(super) struct HalfBlockGrid {
+ /// Width of the grid in number of terminal columns
+ width: u16,
+ /// Height of the grid in number of terminal rows
+ height: u16,
+ /// Represents a single color for each "pixel" arranged in column, row order
+ pixels: Vec>,
+}
+
+impl HalfBlockGrid {
+ /// Create a new `HalfBlockGrid` with the given width and height measured in terminal columns
+ /// and rows respectively.
+ pub(super) fn new(width: u16, height: u16) -> Self {
+ Self {
+ width,
+ height,
+ pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
+ }
+ }
+}
+
+impl Grid for HalfBlockGrid {
+ fn resolution(&self) -> (f64, f64) {
+ (f64::from(self.width), f64::from(self.height) * 2.0)
+ }
+
+ fn save(&self) -> Layer {
+ // Given that we store the pixels in a grid, and that we want to use 2 pixels arranged
+ // vertically to form a single terminal cell, which can be either empty, upper half block,
+ // lower half block or full block, we need examine the pixels in vertical pairs to decide
+ // what character to print in each cell. So these are the 4 states we use to represent each
+ // cell:
+ //
+ // 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
+ // 2. upper: reset, lower: color => '▄' fg: lower color / bg: reset
+ // 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
+ // 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
+ //
+ // Note that because the foreground reset color (i.e. default foreground color) is usually
+ // not the same as the background reset color (i.e. default background color), we need to
+ // swap around the colors for that state (2 reset/color).
+ //
+ // When the upper and lower colors are the same, we could continue to use an upper half
+ // block, but we choose to use a full block instead. This allows us to write unit tests that
+ // treat the cell as a single character instead of two half block characters.
+
+ // first we join each adjacent row together to get an iterator that contains vertical pairs
+ // of pixels, with the lower row being the first element in the pair
+ //
+ // TODO: Whenever I add this as a valid marker, make sure this works fine with
+ // the overridden time_chart drawing-layer-thing.
+ let vertical_color_pairs = self
+ .pixels
+ .iter()
+ .tuples()
+ .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) {
+ (Color::Reset, Color::Reset) => ' ',
+ (Color::Reset, _) => symbols::half_block::LOWER,
+ (_, Color::Reset) => symbols::half_block::UPPER,
+ (&lower, &upper) => {
+ if lower == upper {
+ symbols::half_block::FULL
+ } else {
+ symbols::half_block::UPPER
+ }
+ }
+ })
+ .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 }
+ }
+
+ fn reset(&mut self) {
+ self.pixels.fill(vec![Color::Reset; self.width as usize]);
+ }
+
+ fn paint(&mut self, x: usize, y: usize, color: Color) {
+ self.pixels[y][x] = color;
+ }
+}
diff --git a/src/canvas/components/time_graph/time_chart/points.rs b/src/canvas/components/time_graph/time_chart/points.rs
index 55282cd6..34dc714d 100644
--- a/src/canvas/components/time_graph/time_chart/points.rs
+++ b/src/canvas/components/time_graph/time_chart/points.rs
@@ -46,12 +46,11 @@ impl TimeChart<'_> {
.iter_along_base(times)
.rev()
.map(|(&time, &val)| {
- let from_start: f64 =
- (current_time.duration_since(time).as_millis() as f64).floor();
+ let from_start = current_time.duration_since(time).as_millis() as f64 * -1.0;
// XXX: Should this be generic over dataset.graph_type instead? That would allow us to move
// transformations behind a type - however, that also means that there's some complexity added.
- (-from_start, self.scaling.scale(val))
+ (from_start, self.scaling.scale(val))
})
.tuple_windows()
{
diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs
index 3e5f1dd8..8c1a6f7b 100644
--- a/src/canvas/widgets/network_graph.rs
+++ b/src/canvas/widgets/network_graph.rs
@@ -67,7 +67,7 @@ impl Painter {
let rx_points = &(shared_data.timeseries_data.rx);
let tx_points = &(shared_data.timeseries_data.tx);
let time = &(shared_data.timeseries_data.time);
- let time_start = -(network_widget_state.current_display_time as f64);
+ let time_start = -(network_widget_state.current_display_time as f64) / 1000.0;
let border_style = self.get_border_style(widget_id, app_state.current_widget.widget_id);
let hide_x_labels = should_hide_x_label(
diff --git a/src/collection/amd.rs b/src/collection/amd.rs
index 6cfd8e76..18fe5d73 100644
--- a/src/collection/amd.rs
+++ b/src/collection/amd.rs
@@ -10,9 +10,8 @@ use std::{
use hashbrown::{HashMap, HashSet};
-use crate::{app::layout_manager::UsedWidgets, collection::memory::MemData};
-
use super::linux::utils::is_device_awake;
+use crate::{app::layout_manager::UsedWidgets, collection::memory::MemData};
// TODO: May be able to clean up some of these, Option for example is a bit redundant.
pub struct AmdGpuData {
diff --git a/src/collection/temperature/linux.rs b/src/collection/temperature/linux.rs
index 1f590a1f..30b50f4d 100644
--- a/src/collection/temperature/linux.rs
+++ b/src/collection/temperature/linux.rs
@@ -9,10 +9,9 @@ use anyhow::Result;
use hashbrown::{HashMap, HashSet};
use super::TempSensorData;
-use crate::{app::filter::Filter, collection::linux::utils::is_device_awake};
-
#[cfg(feature = "gpu")]
use crate::collection::amd::get_amd_name;
+use crate::{app::filter::Filter, collection::linux::utils::is_device_awake};
const EMPTY_NAME: &str = "Unknown";
diff --git a/src/utils/logging.rs b/src/utils/logging.rs
index cb610984..b132a655 100644
--- a/src/utils/logging.rs
+++ b/src/utils/logging.rs
@@ -49,7 +49,7 @@ macro_rules! error {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::error!($($x)*)
+ log::error!($($x)*);
}
};
}
@@ -59,7 +59,7 @@ macro_rules! warn {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::warn!($($x)*)
+ log::warn!($($x)*);
}
};
}
@@ -69,7 +69,7 @@ macro_rules! info {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::info!($($x)*)
+ log::info!($($x)*);
}
};
}
@@ -79,7 +79,7 @@ macro_rules! debug {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::debug!($($x)*)
+ log::debug!($($x)*);
}
};
}
@@ -89,7 +89,7 @@ macro_rules! trace {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::trace!($($x)*)
+ log::trace!($($x)*);
}
};
}
@@ -99,25 +99,70 @@ macro_rules! log {
($($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::log!(log::Level::Trace, $($x)*)
+ log::log!(log::Level::Trace, $($x)*);
}
};
($level:expr, $($x:tt)*) => {
#[cfg(feature = "logging")]
{
- log::log!($level, $($x)*)
+ log::log!($level, $($x)*);
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! info_every_n_secs {
+ ($n:expr, $($x:tt)*) => {
+ #[cfg(feature = "logging")]
+ {
+ $crate::log_every_n_secs!(log::Level::Info, $n, $($x)*);
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! log_every_n_secs {
+ ($level:expr, $n:expr, $($x:tt)*) => {
+ #[cfg(feature = "logging")]
+ {
+ static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
+ let since_last_log = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);
+ let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("should be valid").as_secs();
+
+ if now - since_last_log > $n {
+ LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);
+ log::log!($level, $($x)*);
+ }
}
};
}
#[cfg(test)]
mod test {
+ #[cfg(feature = "logging")]
+ /// We do this to ensure that the test logger is only initialized _once_ for
+ /// things like the default test runner that run tests in the same process.
+ ///
+ /// This doesn't do anything if you use something like nextest, which runs
+ /// a test-per-process, but that's fine.
+ fn init_test_logger() {
+ use std::sync::atomic::{AtomicBool, Ordering};
+
+ static LOG_INIT: AtomicBool = AtomicBool::new(false);
+
+ if LOG_INIT.load(Ordering::SeqCst) {
+ return;
+ }
+
+ LOG_INIT.store(true, Ordering::SeqCst);
+ super::init_logger(log::LevelFilter::Trace, None)
+ .expect("initializing the logger should succeed");
+ }
#[cfg(feature = "logging")]
#[test]
fn test_logging_macros() {
- super::init_logger(log::LevelFilter::Trace, None)
- .expect("initializing the logger should succeed");
+ init_test_logger();
error!("This is an error.");
warn!("This is a warning.");
@@ -125,4 +170,12 @@ mod test {
debug!("This is a debug.");
info!("This is a trace.");
}
+
+ #[cfg(feature = "logging")]
+ #[test]
+ fn test_log_every_macros() {
+ init_test_logger();
+
+ info_every_n_secs!(10, "This is an info every 10 seconds.");
+ }
}