refactor: some refactoring while stable graphs (#1715)

* update grids

* asdfadf

* asdf

* b

* update loggers

* some formatting and refactoring

* docs

* some comments

* more docs
This commit is contained in:
Clement Tsang 2025-04-14 02:13:20 -04:00 committed by GitHub
parent c68b190388
commit 146b8596cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 399 additions and 281 deletions

View File

@ -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.
<figure>
<img src="../assets/screenshots/troubleshooting/no_braille.webp" alt="Example of a terminal with no braille font."/>
<figcaption><sub>An example of missing braille fonts in Powershell</sub></figcaption>
</figure>
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
<figcaption><sub>Example using <code>btm --dot_marker</code></sub></figcaption>
</figure>
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
<img src="../assets/screenshots/troubleshooting/cmd_prompt_font.webp" alt="Setting a new font in Command Prompt/PowerShell"/>
</figure>
### 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.

View File

@ -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

View File

@ -12,9 +12,6 @@
//! See <https://github.com/ClementTsang/bottom/pull/918> and <https://github.com/ClementTsang/bottom/pull/937> 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<u16>, // FIXME: (points_rework_v1) isn't this really inefficient to go u16 -> String from utf16?
colors: Vec<Color>,
}
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<char>,
colors: Vec<Color>,
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<Vec<Color>>,
}
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))
}

View File

@ -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 (<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)]
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
/// <https://en.wikipedia.org/wiki/Braille_Patterns> for more info.
///
/// FIXME: (points_rework_v1) isn't this really inefficient to go u16 -> String from utf16?
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>,
}
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<char>,
/// The color of each cell
colors: Vec<Color>,
/// 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<Vec<Color>>,
}
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;
}
}

View File

@ -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()
{

View File

@ -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(

View File

@ -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<Vec> for example is a bit redundant.
pub struct AmdGpuData {

View File

@ -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";

View File

@ -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.");
}
}