mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 06:34:16 +00:00
deps: bump ratatui to 0.26 (#1406)
* deps: bump ratatui to 0.26 * adjust process width * a few nonzero optimizations * add a todo * update comments to be less confusing about time chart
This commit is contained in:
parent
8d84b688b0
commit
b6660610d0
22 changed files with 1469 additions and 487 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -224,6 +224,15 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
|
@ -312,6 +321,19 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concat-string"
|
||||
version = "1.0.1"
|
||||
|
@ -990,12 +1012,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.25.0"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb"
|
||||
checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"itertools 0.12.0",
|
||||
|
@ -1309,18 +1332,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
|
@ -99,7 +99,7 @@ sysinfo = "=0.30.5"
|
|||
thiserror = "1.0.56"
|
||||
time = { version = "0.3.30", features = ["formatting", "macros"] }
|
||||
toml_edit = { version = "0.21.0", features = ["serde"] }
|
||||
tui = { version = "0.25.0", package = "ratatui" }
|
||||
tui = { version = "0.26.0", package = "ratatui" }
|
||||
unicode-segmentation = "1.10.1"
|
||||
unicode-width = "0.1.11"
|
||||
|
||||
|
|
10
src/app.rs
10
src/app.rs
|
@ -2586,7 +2586,7 @@ impl App {
|
|||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
proc_widget_state.table.tui_selected()
|
||||
proc_widget_state.table.ratatui_selected()
|
||||
{
|
||||
let is_tree_mode = matches!(
|
||||
proc_widget_state.mode,
|
||||
|
@ -2614,7 +2614,7 @@ impl App {
|
|||
.get_widget_state(self.current_widget.widget_id - 2)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
proc_widget_state.sort_table.tui_selected()
|
||||
proc_widget_state.sort_table.ratatui_selected()
|
||||
{
|
||||
self.change_process_sort_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
|
@ -2629,7 +2629,7 @@ impl App {
|
|||
.get_widget_state(self.current_widget.widget_id - 1)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
cpu_widget_state.table.tui_selected()
|
||||
cpu_widget_state.table.ratatui_selected()
|
||||
{
|
||||
self.change_cpu_legend_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
|
@ -2644,7 +2644,7 @@ impl App {
|
|||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
temp_widget_state.table.tui_selected()
|
||||
temp_widget_state.table.ratatui_selected()
|
||||
{
|
||||
self.change_temp_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
|
@ -2659,7 +2659,7 @@ impl App {
|
|||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
disk_widget_state.table.tui_selected()
|
||||
disk_widget_state.table.ratatui_selected()
|
||||
{
|
||||
self.change_disk_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
|
|
|
@ -72,7 +72,8 @@ pub struct Painter {
|
|||
|
||||
/// The constraints of a widget relative to its parent.
|
||||
///
|
||||
/// This is used over ratatui's internal representation due to https://github.com/ClementTsang/bottom/issues/896.
|
||||
/// This is used over ratatui's internal representation due to
|
||||
/// <https://github.com/ClementTsang/bottom/issues/896>.
|
||||
pub enum LayoutConstraint {
|
||||
CanvasHandled,
|
||||
Grow,
|
||||
|
@ -498,6 +499,8 @@ impl Painter {
|
|||
}
|
||||
|
||||
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
|
||||
// TODO: Can I remove this? Does ratatui's layout constraints work properly for fixing
|
||||
// https://github.com/ClementTsang/bottom/issues/896 now?
|
||||
fn get_constraints(
|
||||
direction: Direction, constraints: &[LayoutConstraint], area: Rect,
|
||||
) -> Vec<Rect> {
|
||||
|
|
|
@ -144,14 +144,16 @@ impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H
|
|||
self.data.get(self.state.current_index)
|
||||
}
|
||||
|
||||
/// Returns tui-rs' internal selection.
|
||||
pub fn tui_selected(&self) -> Option<usize> {
|
||||
/// Returns ratatui's internal selection.
|
||||
pub fn ratatui_selected(&self) -> Option<usize> {
|
||||
self.state.table_state.selected()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::num::NonZeroU16;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -161,7 +163,7 @@ mod test {
|
|||
|
||||
impl DataToCell<&'static str> for TestType {
|
||||
fn to_cell(
|
||||
&self, _column: &&'static str, _calculated_width: u16,
|
||||
&self, _column: &&'static str, _calculated_width: NonZeroU16,
|
||||
) -> Option<tui::text::Text<'_>> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{max, min},
|
||||
num::NonZeroU16,
|
||||
};
|
||||
|
||||
/// A bound on the width of a column.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ColumnWidthBounds {
|
||||
/// A width of this type is either as long as `min`, but can otherwise shrink and grow up to a point.
|
||||
/// A width of this type is as long as `desired`, but can otherwise shrink and grow up to a point.
|
||||
Soft {
|
||||
/// The desired, calculated width. Take this if possible as the base starting width.
|
||||
desired: u16,
|
||||
|
@ -151,7 +152,7 @@ pub trait CalculateColumnWidths<H> {
|
|||
///
|
||||
/// * `total_width` is the total width on the canvas that the columns can try and work with.
|
||||
/// * `left_to_right` is whether to size from left-to-right (`true`) or right-to-left (`false`).
|
||||
fn calculate_column_widths(&self, total_width: u16, left_to_right: bool) -> Vec<u16>;
|
||||
fn calculate_column_widths(&self, total_width: u16, left_to_right: bool) -> Vec<NonZeroU16>;
|
||||
}
|
||||
|
||||
impl<H, C> CalculateColumnWidths<H> for [C]
|
||||
|
@ -159,19 +160,25 @@ where
|
|||
H: ColumnHeader,
|
||||
C: DataTableColumn<H>,
|
||||
{
|
||||
fn calculate_column_widths(&self, total_width: u16, left_to_right: bool) -> Vec<u16> {
|
||||
fn calculate_column_widths(&self, total_width: u16, left_to_right: bool) -> Vec<NonZeroU16> {
|
||||
use itertools::Either;
|
||||
|
||||
const COLUMN_SPACING: u16 = 1;
|
||||
|
||||
#[inline]
|
||||
fn stop_allocating_space(desired: u16, available: u16) -> bool {
|
||||
desired > available || desired == 0
|
||||
}
|
||||
|
||||
let mut total_width_left = total_width;
|
||||
let mut calculated_widths = vec![0; self.len()];
|
||||
let mut calculated_widths = vec![];
|
||||
let columns = if left_to_right {
|
||||
Either::Left(self.iter().zip(calculated_widths.iter_mut()))
|
||||
Either::Left(self.iter())
|
||||
} else {
|
||||
Either::Right(self.iter().zip(calculated_widths.iter_mut()).rev())
|
||||
Either::Right(self.iter().rev())
|
||||
};
|
||||
|
||||
let mut num_columns = 0;
|
||||
for (column, calculated_width) in columns {
|
||||
for column in columns {
|
||||
if column.is_hidden() {
|
||||
continue;
|
||||
}
|
||||
|
@ -196,41 +203,60 @@ where
|
|||
);
|
||||
let space_taken = min(min(soft_limit, *desired), total_width_left);
|
||||
|
||||
if min_width > space_taken || min_width == 0 {
|
||||
if stop_allocating_space(space_taken, total_width_left) {
|
||||
break;
|
||||
} else if space_taken > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
||||
*calculated_width = space_taken;
|
||||
num_columns += 1;
|
||||
} else {
|
||||
total_width_left =
|
||||
total_width_left.saturating_sub(space_taken + COLUMN_SPACING);
|
||||
|
||||
// SAFETY: This is safe as we call `stop_allocating_space` which checks that
|
||||
// the value pushed is greater than zero.
|
||||
unsafe {
|
||||
calculated_widths.push(NonZeroU16::new_unchecked(space_taken));
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnWidthBounds::Hard(width) => {
|
||||
let min_width = *width;
|
||||
if min_width > total_width_left || min_width == 0 {
|
||||
if stop_allocating_space(min_width, total_width_left) {
|
||||
break;
|
||||
} else if min_width > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(min_width + 1);
|
||||
*calculated_width = min_width;
|
||||
num_columns += 1;
|
||||
} else {
|
||||
total_width_left =
|
||||
total_width_left.saturating_sub(min_width + COLUMN_SPACING);
|
||||
|
||||
// SAFETY: This is safe as we call `stop_allocating_space` which checks that
|
||||
// the value pushed is greater than zero.
|
||||
unsafe {
|
||||
calculated_widths.push(NonZeroU16::new_unchecked(min_width));
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnWidthBounds::FollowHeader => {
|
||||
let min_width = column.header_len() as u16;
|
||||
if min_width > total_width_left || min_width == 0 {
|
||||
if stop_allocating_space(min_width, total_width_left) {
|
||||
break;
|
||||
} else if min_width > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(min_width + 1);
|
||||
*calculated_width = min_width;
|
||||
num_columns += 1;
|
||||
} else {
|
||||
total_width_left =
|
||||
total_width_left.saturating_sub(min_width + COLUMN_SPACING);
|
||||
|
||||
// SAFETY: This is safe as we call `stop_allocating_space` which checks that
|
||||
// the value pushed is greater than zero.
|
||||
unsafe {
|
||||
calculated_widths.push(NonZeroU16::new_unchecked(min_width));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if num_columns > 0 {
|
||||
// Redistribute remaining.
|
||||
let mut num_dist = num_columns;
|
||||
let amount_per_slot = total_width_left / num_dist;
|
||||
if !calculated_widths.is_empty() {
|
||||
if !left_to_right {
|
||||
calculated_widths.reverse();
|
||||
}
|
||||
|
||||
// Redistribute remaining space.
|
||||
let mut num_dist = calculated_widths.len() as u16;
|
||||
let amount_per_slot = total_width_left / num_dist; // Safe from DBZ by above empty check.
|
||||
total_width_left %= num_dist;
|
||||
|
||||
for width in calculated_widths.iter_mut() {
|
||||
|
@ -238,16 +264,14 @@ where
|
|||
break;
|
||||
}
|
||||
|
||||
if *width > 0 {
|
||||
if total_width_left > 0 {
|
||||
*width += amount_per_slot + 1;
|
||||
total_width_left -= 1;
|
||||
} else {
|
||||
*width += amount_per_slot;
|
||||
}
|
||||
|
||||
num_dist -= 1;
|
||||
if total_width_left > 0 {
|
||||
*width = width.saturating_add(amount_per_slot + 1);
|
||||
total_width_left -= 1;
|
||||
} else {
|
||||
*width = width.saturating_add(amount_per_slot);
|
||||
}
|
||||
|
||||
num_dist -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::num::NonZeroU16;
|
||||
|
||||
use tui::{text::Text, widgets::Row};
|
||||
|
||||
use super::{ColumnHeader, DataTableColumn};
|
||||
|
@ -8,7 +10,7 @@ where
|
|||
H: ColumnHeader,
|
||||
{
|
||||
/// Given data, a column, and its corresponding width, return what should be displayed in the [`DataTable`](super::DataTable).
|
||||
fn to_cell(&self, column: &H, calculated_width: u16) -> Option<Text<'_>>;
|
||||
fn to_cell(&self, column: &H, calculated_width: NonZeroU16) -> Option<Text<'_>>;
|
||||
|
||||
/// Apply styling to the generated [`Row`] of cells.
|
||||
///
|
||||
|
|
|
@ -249,18 +249,7 @@ where
|
|||
};
|
||||
let mut table = Table::new(
|
||||
rows,
|
||||
&(self
|
||||
.state
|
||||
.calculated_widths
|
||||
.iter()
|
||||
.filter_map(|&width| {
|
||||
if width == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Constraint::Length(width))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()),
|
||||
self.state.calculated_widths.iter().map(|nzu| nzu.get()),
|
||||
)
|
||||
.block(block)
|
||||
.highlight_style(highlight_style)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, marker::PhantomData};
|
||||
use std::{borrow::Cow, marker::PhantomData, num::NonZeroU16};
|
||||
|
||||
use concat_string::concat_string;
|
||||
use itertools::Itertools;
|
||||
|
@ -52,18 +52,17 @@ pub struct Sortable {
|
|||
/// and therefore only [`Unsortable`] and [`Sortable`] can implement it.
|
||||
pub trait SortType: private::Sealed {
|
||||
/// Constructs the table header.
|
||||
fn build_header<H, C>(&self, columns: &[C], widths: &[u16]) -> Row<'_>
|
||||
fn build_header<H, C>(&self, columns: &[C], widths: &[NonZeroU16]) -> Row<'_>
|
||||
where
|
||||
H: ColumnHeader,
|
||||
C: DataTableColumn<H>,
|
||||
{
|
||||
Row::new(columns.iter().zip(widths).filter_map(|(c, &width)| {
|
||||
if width == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(truncate_to_text(&c.header(), width))
|
||||
}
|
||||
}))
|
||||
Row::new(
|
||||
columns
|
||||
.iter()
|
||||
.zip(widths)
|
||||
.map(|(c, &width)| truncate_to_text(&c.header(), width.get())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +78,7 @@ mod private {
|
|||
impl SortType for Unsortable {}
|
||||
|
||||
impl SortType for Sortable {
|
||||
fn build_header<H, C>(&self, columns: &[C], widths: &[u16]) -> Row<'_>
|
||||
fn build_header<H, C>(&self, columns: &[C], widths: &[NonZeroU16]) -> Row<'_>
|
||||
where
|
||||
H: ColumnHeader,
|
||||
C: DataTableColumn<H>,
|
||||
|
@ -92,17 +91,17 @@ impl SortType for Sortable {
|
|||
.iter()
|
||||
.zip(widths)
|
||||
.enumerate()
|
||||
.filter_map(|(index, (c, &width))| {
|
||||
if width == 0 {
|
||||
None
|
||||
} else if index == self.sort_index {
|
||||
.map(|(index, (c, &width))| {
|
||||
if index == self.sort_index {
|
||||
let arrow = match self.order {
|
||||
SortOrder::Ascending => UP_ARROW,
|
||||
SortOrder::Descending => DOWN_ARROW,
|
||||
};
|
||||
Some(truncate_to_text(&concat_string!(c.header(), arrow), width))
|
||||
// TODO: I think I can get away with removing the truncate_to_text call since
|
||||
// I almost always bind to at least the header size...
|
||||
truncate_to_text(&concat_string!(c.header(), arrow), width.get())
|
||||
} else {
|
||||
Some(truncate_to_text(&c.header(), width))
|
||||
truncate_to_text(&c.header(), width.get())
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
@ -331,7 +330,7 @@ where
|
|||
.iter()
|
||||
.map(|width| {
|
||||
let entry_start = start;
|
||||
start += width + 1; // +1 for the gap b/w cols.
|
||||
start += width.get() + 1; // +1 for the gap b/w cols.
|
||||
|
||||
entry_start
|
||||
})
|
||||
|
@ -361,7 +360,7 @@ mod test {
|
|||
|
||||
impl DataToCell<ColumnType> for TestType {
|
||||
fn to_cell(
|
||||
&self, _column: &ColumnType, _calculated_width: u16,
|
||||
&self, _column: &ColumnType, _calculated_width: NonZeroU16,
|
||||
) -> Option<tui::text::Text<'_>> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::num::NonZeroU16;
|
||||
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
|
@ -21,11 +23,11 @@ pub struct DataTableState {
|
|||
/// The direction of the last attempted scroll.
|
||||
pub scroll_direction: ScrollDirection,
|
||||
|
||||
/// tui-rs' internal table state.
|
||||
/// ratatui's internal table state.
|
||||
pub table_state: TableState,
|
||||
|
||||
/// The calculated widths.
|
||||
pub calculated_widths: Vec<u16>,
|
||||
pub calculated_widths: Vec<NonZeroU16>,
|
||||
|
||||
/// The current inner [`Rect`].
|
||||
pub inner_rect: Rect,
|
||||
|
|
|
@ -51,8 +51,8 @@ pub struct TimeGraph<'a> {
|
|||
/// Any legend constraints.
|
||||
pub legend_constraints: Option<(Constraint, Constraint)>,
|
||||
|
||||
/// The marker type. Unlike tui-rs' native charts, we assume
|
||||
/// only a single type of market.
|
||||
/// The marker type. Unlike ratatui's native charts, we assume
|
||||
/// only a single type of marker.
|
||||
pub marker: Marker,
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,18 @@
|
|||
//! Vendored from <https://github.com/fdehau/tui-rs/blob/fafad6c96109610825aad89c4bba5253e01101ed/src/widgets/canvas/mod.rs>.
|
||||
//! Main difference is in the Braille rendering, which can now effectively be done in a single layer without the effects
|
||||
//! of doing it all in a single layer via the normal tui-rs crate. This means you can do it all in a single pass, with
|
||||
//! just one string alloc and no resets.
|
||||
//! Vendored from <https://github.com/fdehau/tui-rs/blob/fafad6c96109610825aad89c4bba5253e01101ed/src/widgets/canvas/mod.rs>
|
||||
//! and <https://github.com/ratatui-org/ratatui/blob/c8dd87918d44fff6d4c3c78e1fc821a3275db1ae/src/widgets/canvas.rs>.
|
||||
//!
|
||||
//! The main thing this is pulled in for is overriding how `BrailleGrid`'s draw logic works, as changing it is
|
||||
//! needed in order to draw all datasets in only one layer back in [`super::TimeChart::render`]. More specifically,
|
||||
//! the current implementation in ratatui `|=`s all the cells together if they overlap, but since we are smashing
|
||||
//! all the layers together which may have different colours, we instead just _replace_ whatever was in that cell
|
||||
//! with the newer colour + character.
|
||||
//!
|
||||
//! See <https://github.com/ClementTsang/bottom/pull/918> and <https://github.com/ClementTsang/bottom/pull/937> for the
|
||||
//! original motivation.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{fmt::Debug, iter::zip};
|
||||
|
||||
use itertools::Itertools;
|
||||
use tui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
|
@ -128,7 +136,7 @@ pub struct Label<'a> {
|
|||
#[derive(Debug, Clone)]
|
||||
struct Layer {
|
||||
string: String,
|
||||
colors: Vec<Color>,
|
||||
colors: Vec<(Color, Color)>,
|
||||
}
|
||||
|
||||
trait Grid: Debug {
|
||||
|
@ -179,7 +187,7 @@ impl Grid for BrailleGrid {
|
|||
fn save(&self) -> Layer {
|
||||
Layer {
|
||||
string: String::from_utf16(&self.cells).unwrap(),
|
||||
colors: self.colors.clone(),
|
||||
colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +255,7 @@ impl Grid for CharGrid {
|
|||
fn save(&self) -> Layer {
|
||||
Layer {
|
||||
string: self.cells.iter().collect(),
|
||||
colors: self.colors.clone(),
|
||||
colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,6 +285,113 @@ pub struct Painter<'a, '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 width(&self) -> u16 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> u16 {
|
||||
self.height
|
||||
}
|
||||
|
||||
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 overriden
|
||||
// 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<'a, 'b> Painter<'a, 'b> {
|
||||
/// Convert the (x, y) coordinates to location of a point on the grid
|
||||
///
|
||||
|
@ -366,7 +481,7 @@ impl<'a> Context<'a> {
|
|||
symbols::Marker::Block => Box::new(CharGrid::new(width, height, '█')),
|
||||
symbols::Marker::Bar => Box::new(CharGrid::new(width, height, '▄')),
|
||||
symbols::Marker::Braille => Box::new(BrailleGrid::new(width, height)),
|
||||
symbols::Marker::HalfBlock => Box::new(CharGrid::new(width, height, '▀')),
|
||||
symbols::Marker::HalfBlock => Box::new(HalfBlockGrid::new(width, height)),
|
||||
};
|
||||
Context {
|
||||
x_bounds,
|
||||
|
@ -507,7 +622,7 @@ where
|
|||
// Paint whatever is in the ctx.
|
||||
let layer = ctx.grid.save();
|
||||
|
||||
for (i, (ch, color)) in layer
|
||||
for (i, (ch, (fg, bg))) in layer
|
||||
.string
|
||||
.chars()
|
||||
.zip(layer.colors.into_iter())
|
||||
|
@ -517,7 +632,8 @@ where
|
|||
let (x, y) = (i % width, i / width);
|
||||
buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
|
||||
.set_char(ch)
|
||||
.set_fg(color);
|
||||
.set_fg(fg)
|
||||
.set_bg(bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
215
src/canvas/components/tui_widget/time_chart/points.rs
Normal file
215
src/canvas/components/tui_widget/time_chart/points.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
use tui::{
|
||||
style::Color,
|
||||
widgets::{
|
||||
canvas::{Line as CanvasLine, Points},
|
||||
GraphType,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::utils::general::partial_ordering;
|
||||
|
||||
use super::{Context, Dataset, Point, TimeChart};
|
||||
|
||||
impl TimeChart<'_> {
|
||||
pub(crate) fn draw_points(&self, ctx: &mut Context<'_>) {
|
||||
// Idea is to:
|
||||
// - Go over all datasets, determine *where* a point will be drawn.
|
||||
// - Last point wins for what gets drawn.
|
||||
// - We set _all_ points for all datasets before actually rendering.
|
||||
//
|
||||
// By doing this, it's a bit more efficient from my experience than looping
|
||||
// over each dataset and rendering a new layer each time.
|
||||
//
|
||||
// See <https://github.com/ClementTsang/bottom/pull/918> and <https://github.com/ClementTsang/bottom/pull/937>
|
||||
// for the original motivation.
|
||||
//
|
||||
// We also additionally do some interpolation logic because we may get caught missing some points
|
||||
// when drawing, but we generally want to avoid jarring gaps between the edges when there's
|
||||
// a point that is off screen and so a line isn't drawn (right edge generally won't have this issue
|
||||
// issue but it can happen in some cases).
|
||||
|
||||
for dataset in &self.datasets {
|
||||
let color = dataset.style.fg.unwrap_or(Color::Reset);
|
||||
|
||||
let start_bound = self.x_axis.bounds[0];
|
||||
let end_bound = self.x_axis.bounds[1];
|
||||
|
||||
let (start_index, interpolate_start) = get_start(dataset, start_bound);
|
||||
let (end_index, interpolate_end) = get_end(dataset, end_bound);
|
||||
|
||||
let data_slice = &dataset.data[start_index..end_index];
|
||||
|
||||
if let Some(interpolate_start) = interpolate_start {
|
||||
if let (Some(older_point), Some(newer_point)) = (
|
||||
dataset.data.get(interpolate_start),
|
||||
dataset.data.get(interpolate_start + 1),
|
||||
) {
|
||||
let interpolated_point = (
|
||||
self.x_axis.bounds[0],
|
||||
interpolate_point(older_point, newer_point, self.x_axis.bounds[0]),
|
||||
);
|
||||
|
||||
if let GraphType::Line = dataset.graph_type {
|
||||
ctx.draw(&CanvasLine {
|
||||
x1: interpolated_point.0,
|
||||
y1: interpolated_point.1,
|
||||
x2: newer_point.0,
|
||||
y2: newer_point.1,
|
||||
color,
|
||||
});
|
||||
} else {
|
||||
ctx.draw(&Points {
|
||||
coords: &[interpolated_point],
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let GraphType::Line = dataset.graph_type {
|
||||
for data in data_slice.windows(2) {
|
||||
ctx.draw(&CanvasLine {
|
||||
x1: data[0].0,
|
||||
y1: data[0].1,
|
||||
x2: data[1].0,
|
||||
y2: data[1].1,
|
||||
color,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ctx.draw(&Points {
|
||||
coords: data_slice,
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(interpolate_end) = interpolate_end {
|
||||
if let (Some(older_point), Some(newer_point)) = (
|
||||
dataset.data.get(interpolate_end - 1),
|
||||
dataset.data.get(interpolate_end),
|
||||
) {
|
||||
let interpolated_point = (
|
||||
self.x_axis.bounds[1],
|
||||
interpolate_point(older_point, newer_point, self.x_axis.bounds[1]),
|
||||
);
|
||||
|
||||
if let GraphType::Line = dataset.graph_type {
|
||||
ctx.draw(&CanvasLine {
|
||||
x1: older_point.0,
|
||||
y1: older_point.1,
|
||||
x2: interpolated_point.0,
|
||||
y2: interpolated_point.1,
|
||||
color,
|
||||
});
|
||||
} else {
|
||||
ctx.draw(&Points {
|
||||
coords: &[interpolated_point],
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the start index and potential interpolation index given the start time and the dataset.
|
||||
fn get_start(dataset: &Dataset<'_>, start_bound: f64) -> (usize, Option<usize>) {
|
||||
match dataset
|
||||
.data
|
||||
.binary_search_by(|(x, _y)| partial_ordering(x, &start_bound))
|
||||
{
|
||||
Ok(index) => (index, None),
|
||||
Err(index) => (index, index.checked_sub(1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the end position and potential interpolation index given the end time and the dataset.
|
||||
fn get_end(dataset: &Dataset<'_>, end_bound: f64) -> (usize, Option<usize>) {
|
||||
match dataset
|
||||
.data
|
||||
.binary_search_by(|(x, _y)| partial_ordering(x, &end_bound))
|
||||
{
|
||||
// In the success case, this means we found an index. Add one since we want to include this index and we
|
||||
// expect to use the returned index as part of a (m..n) range.
|
||||
Ok(index) => (index.saturating_add(1), None),
|
||||
// In the fail case, this means we did not find an index, and the returned index is where one would *insert*
|
||||
// the location. This index is where one would insert to fit inside the dataset - and since this is an end
|
||||
// bound, index is, in a sense, already "+1" for our range later.
|
||||
Err(index) => (index, {
|
||||
let sum = index.checked_add(1);
|
||||
match sum {
|
||||
Some(s) if s < dataset.data.len() => sum,
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the y-axis value for a given `x`, given two points to draw a line between.
|
||||
fn interpolate_point(older_point: &Point, newer_point: &Point, x: f64) -> f64 {
|
||||
let delta_x = newer_point.0 - older_point.0;
|
||||
let delta_y = newer_point.1 - older_point.1;
|
||||
let slope = delta_y / delta_x;
|
||||
|
||||
(older_point.1 + (x - older_point.0) * slope).max(0.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn time_chart_test_interpolation() {
|
||||
let data = [(-3.0, 8.0), (-1.0, 6.0), (0.0, 5.0)];
|
||||
|
||||
assert_eq!(interpolate_point(&data[1], &data[2], 0.0), 5.0);
|
||||
assert_eq!(interpolate_point(&data[1], &data[2], -0.25), 5.25);
|
||||
assert_eq!(interpolate_point(&data[1], &data[2], -0.5), 5.5);
|
||||
assert_eq!(interpolate_point(&data[0], &data[1], -1.0), 6.0);
|
||||
assert_eq!(interpolate_point(&data[0], &data[1], -1.5), 6.5);
|
||||
assert_eq!(interpolate_point(&data[0], &data[1], -2.0), 7.0);
|
||||
assert_eq!(interpolate_point(&data[0], &data[1], -2.5), 7.5);
|
||||
assert_eq!(interpolate_point(&data[0], &data[1], -3.0), 8.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_chart_empty_dataset() {
|
||||
let data = [];
|
||||
let dataset = Dataset::default().data(&data);
|
||||
|
||||
assert_eq!(get_start(&dataset, -100.0), (0, None));
|
||||
assert_eq!(get_start(&dataset, -3.0), (0, None));
|
||||
|
||||
assert_eq!(get_end(&dataset, 0.0), (0, None));
|
||||
assert_eq!(get_end(&dataset, 100.0), (0, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_chart_test_data_trimming() {
|
||||
let data = [
|
||||
(-3.0, 8.0),
|
||||
(-2.5, 15.0),
|
||||
(-2.0, 9.0),
|
||||
(-1.0, 6.0),
|
||||
(0.0, 5.0),
|
||||
];
|
||||
let dataset = Dataset::default().data(&data);
|
||||
|
||||
// Test start point cases (miss and hit)
|
||||
assert_eq!(get_start(&dataset, -100.0), (0, None));
|
||||
assert_eq!(get_start(&dataset, -3.0), (0, None));
|
||||
assert_eq!(get_start(&dataset, -2.8), (1, Some(0)));
|
||||
assert_eq!(get_start(&dataset, -2.5), (1, None));
|
||||
assert_eq!(get_start(&dataset, -2.4), (2, Some(1)));
|
||||
|
||||
// Test end point cases (miss and hit)
|
||||
assert_eq!(get_end(&dataset, -2.5), (2, None));
|
||||
assert_eq!(get_end(&dataset, -2.4), (2, Some(3)));
|
||||
assert_eq!(get_end(&dataset, -1.4), (3, Some(4)));
|
||||
assert_eq!(get_end(&dataset, -1.0), (4, None));
|
||||
assert_eq!(get_end(&dataset, 0.0), (5, None));
|
||||
assert_eq!(get_end(&dataset, 1.0), (5, None));
|
||||
assert_eq!(get_end(&dataset, 100.0), (5, None));
|
||||
}
|
||||
}
|
|
@ -345,8 +345,8 @@ fn adjust_network_data_point(
|
|||
// So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and
|
||||
// probably something like 438.75?
|
||||
//
|
||||
// So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max
|
||||
// value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will
|
||||
// So, how do we do this in ratatui? Well, if we are using intervals that tie in perfectly to the max
|
||||
// value we want... then it's actually not that hard. Since ratatui accepts a vector as labels and will
|
||||
// properly space them all out... we just work with that and space it out properly.
|
||||
//
|
||||
// Dynamic chart idea based off of FreeNAS's chart design.
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{cmp::Ordering, num::NonZeroUsize};
|
||||
|
||||
use tui::text::{Line, Span, Text};
|
||||
use tui::{
|
||||
style::Style,
|
||||
text::{Line, Span, Text},
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
|
@ -64,6 +67,8 @@ pub fn get_decimal_prefix(quantity: u64, unit: &str) -> (f64, String) {
|
|||
pub fn truncate_to_text<'a, U: Into<usize>>(content: &str, width: U) -> Text<'a> {
|
||||
Text {
|
||||
lines: vec![Line::from(vec![Span::raw(truncate_str(content, width))])],
|
||||
style: Style::default(),
|
||||
alignment: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, time::Instant};
|
||||
use std::{borrow::Cow, num::NonZeroU16, time::Instant};
|
||||
|
||||
use concat_string::concat_string;
|
||||
use tui::{style::Style, text::Text, widgets::Row};
|
||||
|
@ -81,9 +81,11 @@ impl CpuWidgetTableData {
|
|||
}
|
||||
|
||||
impl DataToCell<CpuWidgetColumn> for CpuWidgetTableData {
|
||||
fn to_cell(&self, column: &CpuWidgetColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
fn to_cell(&self, column: &CpuWidgetColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
const CPU_TRUNCATE_BREAKPOINT: u16 = 5;
|
||||
|
||||
let calculated_width = calculated_width.get();
|
||||
|
||||
// This is a bit of a hack, but apparently we can avoid having to do any fancy checks
|
||||
// of showing the "All" on a specific column if the other is hidden by just always
|
||||
// showing it on the CPU (first) column - if there isn't room for it, it will just collapse
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, cmp::max};
|
||||
use std::{borrow::Cow, cmp::max, num::NonZeroU16};
|
||||
|
||||
use kstring::KString;
|
||||
use tui::text::Text;
|
||||
|
@ -128,11 +128,8 @@ impl ColumnHeader for DiskWidgetColumn {
|
|||
}
|
||||
|
||||
impl DataToCell<DiskWidgetColumn> for DiskWidgetData {
|
||||
fn to_cell(&self, column: &DiskWidgetColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
if calculated_width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn to_cell(&self, column: &DiskWidgetColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
let calculated_width = calculated_width.get();
|
||||
let text = match column {
|
||||
DiskWidgetColumn::Disk => truncate_to_text(&self.name, calculated_width),
|
||||
DiskWidgetColumn::Mount => truncate_to_text(&self.mount_point, calculated_width),
|
||||
|
|
|
@ -89,7 +89,7 @@ fn make_column(column: ProcColumn) -> SortColumn<ProcColumn> {
|
|||
TotalRead => SortColumn::hard(TotalRead, 8).default_descending(),
|
||||
TotalWrite => SortColumn::hard(TotalWrite, 8).default_descending(),
|
||||
User => SortColumn::soft(User, Some(0.05)),
|
||||
State => SortColumn::hard(State, 7),
|
||||
State => SortColumn::hard(State, 9),
|
||||
Time => SortColumn::new(Time),
|
||||
#[cfg(feature = "gpu")]
|
||||
GpuMem => SortColumn::new(GpuMem).default_descending(),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
cmp::{max, Ordering},
|
||||
fmt::Display,
|
||||
num::NonZeroU16,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
@ -299,10 +300,8 @@ impl ProcWidgetData {
|
|||
}
|
||||
|
||||
impl DataToCell<ProcColumn> for ProcWidgetData {
|
||||
fn to_cell(&self, column: &ProcColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
if calculated_width == 0 {
|
||||
return None;
|
||||
}
|
||||
fn to_cell(&self, column: &ProcColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
let calculated_width = calculated_width.get();
|
||||
|
||||
// TODO: Optimize the string allocations here...
|
||||
// TODO: Also maybe just pull in the to_string call but add a variable for the differences.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, num::NonZeroU16};
|
||||
|
||||
use tui::text::Text;
|
||||
|
||||
|
@ -16,12 +16,8 @@ impl ColumnHeader for SortTableColumn {
|
|||
}
|
||||
|
||||
impl DataToCell<SortTableColumn> for &'static str {
|
||||
fn to_cell(&self, _column: &SortTableColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
if calculated_width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(truncate_to_text(self, calculated_width))
|
||||
fn to_cell(&self, _column: &SortTableColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
Some(truncate_to_text(self, calculated_width.get()))
|
||||
}
|
||||
|
||||
fn column_widths<C: DataTableColumn<SortTableColumn>>(data: &[Self], _columns: &[C]) -> Vec<u16>
|
||||
|
@ -33,12 +29,8 @@ impl DataToCell<SortTableColumn> for &'static str {
|
|||
}
|
||||
|
||||
impl DataToCell<SortTableColumn> for Cow<'static, str> {
|
||||
fn to_cell(&self, _column: &SortTableColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
if calculated_width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(truncate_to_text(self, calculated_width))
|
||||
fn to_cell(&self, _column: &SortTableColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
Some(truncate_to_text(self, calculated_width.get()))
|
||||
}
|
||||
|
||||
fn column_widths<C: DataTableColumn<SortTableColumn>>(data: &[Self], _columns: &[C]) -> Vec<u16>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, cmp::max};
|
||||
use std::{borrow::Cow, cmp::max, num::NonZeroU16};
|
||||
|
||||
use concat_string::concat_string;
|
||||
use kstring::KString;
|
||||
|
@ -55,14 +55,10 @@ impl TempWidgetData {
|
|||
}
|
||||
|
||||
impl DataToCell<TempWidgetColumn> for TempWidgetData {
|
||||
fn to_cell(&self, column: &TempWidgetColumn, calculated_width: u16) -> Option<Text<'_>> {
|
||||
if calculated_width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn to_cell(&self, column: &TempWidgetColumn, calculated_width: NonZeroU16) -> Option<Text<'_>> {
|
||||
Some(match column {
|
||||
TempWidgetColumn::Sensor => truncate_to_text(&self.sensor, calculated_width),
|
||||
TempWidgetColumn::Temp => truncate_to_text(&self.temperature(), calculated_width),
|
||||
TempWidgetColumn::Sensor => truncate_to_text(&self.sensor, calculated_width.get()),
|
||||
TempWidgetColumn::Temp => truncate_to_text(&self.temperature(), calculated_width.get()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue