repo_description: Yet another cross-platform graphical process/system monitor.
repo_topics: bottom btm cli cross-platform monitoring rust terminal top tui
code: use std::{borrow::Cow, cmp::max, num::NonZeroU16};
  
  use serde::Deserialize;
  
  use crate::{
      app::{AppConfigFields, data::TypedTemperature},
      canvas::components::data_table::{
          ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn,
          SortDataTable, SortDataTableProps, SortOrder, SortsRow,
      },
      options::config::style::Styles,
      utils::general::sort_partial_fn,
  };
  
  #[derive(Clone, Debug)]
  pub struct TempWidgetData {
      pub sensor: String,
      pub temperature: Option<TypedTemperature>,
  }
  
  #[derive(Debug, Clone)]
  #[cfg_attr(
      feature = "generate_schema",
      derive(schemars::JsonSchema, strum::VariantArray)
  )]
  #[cfg_attr(test, derive(PartialEq, Eq))]
  pub enum TempWidgetColumn {
      Sensor,
      Temperature,
  }
  
  impl<'de> Deserialize<'de> for TempWidgetColumn {
      fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
      where
          D: serde::Deserializer<'de>,
      {
          let value = String::deserialize(deserializer)?.to_lowercase();
          match value.as_str() {
              "sensor" => Ok(TempWidgetColumn::Sensor),
              "temp" | "temperature" => Ok(TempWidgetColumn::Temperature),
              _ => Err(serde::de::Error::custom(
                  "doesn't match any temperature column name",
              )),
          }
      }
  }
  
  impl TempWidgetColumn {
      /// An ugly hack to generate the JSON schema.
      #[cfg(feature = "generate_schema")]
      pub fn get_schema_names(&self) -> &[&'static str] {
          match self {
              TempWidgetColumn::Sensor => &["Sensor"],
              TempWidgetColumn::Temperature => &["Temp", "Temperature"],
          }
      }
  }
  
  impl ColumnHeader for TempWidgetColumn {
      fn text(&self) -> Cow<'static, str> {
          match self {
              TempWidgetColumn::Sensor => "Sensor(s)".into(),
              TempWidgetColumn::Temperature => "Temp(t)".into(),
          }
      }
  }
  
  impl TempWidgetData {
      pub fn temperature(&self) -> Cow<'static, str> {
          match &self.temperature {
              Some(temp) => temp.to_string().into(),
              None => "N/A".into(),
          }
      }
  }
  
  impl DataToCell<TempWidgetColumn> for TempWidgetData {
      fn to_cell_text(
          &self, column: &TempWidgetColumn, _calculated_width: NonZeroU16,
      ) -> Option<Cow<'static, str>> {
          Some(match column {
              TempWidgetColumn::Sensor => self.sensor.clone().into(),
              TempWidgetColumn::Temperature => self.temperature(),
          })
      }
  
      fn column_widths<C: DataTableColumn<TempWidgetColumn>>(
          data: &[TempWidgetData], _columns: &[C],
      ) -> Vec<u16>
      where
          Self: Sized,
      {
          let mut widths = vec![0; 2];
  
          data.iter().for_each(|row| {
              widths[0] = max(widths[0], row.sensor.len() as u16);
              widths[1] = max(widths[1], row.temperature().len() as u16);
          });
  
          widths
      }
  }
  
  impl SortsRow for TempWidgetColumn {
      type DataType = TempWidgetData;
  
      fn sort_data(&self, data: &mut [Self::DataType], descending: bool) {
          match self {
              TempWidgetColumn::Sensor => {
                  data.sort_by(move |a, b| sort_partial_fn(descending)(&a.sensor, &b.sensor));
              }
              TempWidgetColumn::Temperature => {
                  data.sort_by(|a, b| sort_partial_fn(descending)(&a.temperature, &b.temperature));
              }
          }
      }
  }
  
  pub struct TempWidgetState {
      pub table: SortDataTable<TempWidgetData, TempWidgetColumn>,
      pub force_update_data: bool,
  }
  
  impl TempWidgetState {
      pub(crate) fn new(config: &AppConfigFields, palette: &Styles) -> Self {
          let columns = [
              SortColumn::soft(TempWidgetColumn::Sensor, Some(0.8)),
              SortColumn::soft(TempWidgetColumn::Temperature, None).default_descending(),
          ];
  
          let props = SortDataTableProps {
              inner: DataTableProps {
                  title: Some(" Temperatures ".into()),
                  table_gap: config.table_gap,
                  left_to_right: false,
                  is_basic: config.use_basic_mode,
                  show_table_scroll_position: config.show_table_scroll_position,
                  show_current_entry_when_unfocused: false,
              },
              // This is hard-coded, but there's only two columns so it's fine.
              sort_index: match config.default_temp_sort_column {
                  Some(TempWidgetColumn::Temperature) => 1,
                  Some(TempWidgetColumn::Sensor) | None => 0,
              },
              order: SortOrder::Ascending,
          };
  
          let styling = DataTableStyling::from_palette(palette);
  
          Self {
              table: SortDataTable::new_sortable(columns, props, styling),
              force_update_data: false,
          }
      }
  
      /// Forces an update of the data stored.
      #[inline]
      pub fn force_data_update(&mut self) {
          self.force_update_data = true;
      }
  
      /// Update the current table data.
      pub fn set_table_data(&mut self, data: &[TempWidgetData]) {
          let mut data = data.to_vec();
          if let Some(column) = self.table.columns.get(self.table.sort_index()) {
              column.sort_by(&mut data, self.table.order());
          }
          self.table.set_data(data);
          self.force_update_data = false;
      }
  }
  