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.
Example of a terminal with no braille font.
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 Setting a new font in Command Prompt/PowerShell +### 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."); + } }