mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-05-03 06:00:54 +02:00
Basic temp
This commit is contained in:
parent
18bce9f0a0
commit
a78edc88c0
33
src/app.rs
33
src/app.rs
@ -29,6 +29,7 @@ use frozen_state::FrozenState;
|
|||||||
use crate::{
|
use crate::{
|
||||||
canvas::Painter,
|
canvas::Painter,
|
||||||
constants,
|
constants,
|
||||||
|
data_conversion::ConvertedData,
|
||||||
tuine::{Application, Element, Flex, Status, ViewContext},
|
tuine::{Application, Element, Flex, Status, ViewContext},
|
||||||
units::data_units::DataUnit,
|
units::data_units::DataUnit,
|
||||||
Pid,
|
Pid,
|
||||||
@ -129,7 +130,13 @@ impl Default for CurrentScreen {
|
|||||||
pub enum AppMessages {
|
pub enum AppMessages {
|
||||||
Update(Box<data_harvester::Data>),
|
Update(Box<data_harvester::Data>),
|
||||||
OpenHelp,
|
OpenHelp,
|
||||||
KillProcess { to_kill: Vec<Pid> },
|
ConfirmKillProcess {
|
||||||
|
to_kill: Vec<Pid>,
|
||||||
|
},
|
||||||
|
KillProcess {
|
||||||
|
to_kill: Vec<Pid>,
|
||||||
|
signal: Option<i32>,
|
||||||
|
},
|
||||||
ToggleFreeze,
|
ToggleFreeze,
|
||||||
Reset,
|
Reset,
|
||||||
Clean,
|
Clean,
|
||||||
@ -209,27 +216,34 @@ impl AppState {
|
|||||||
impl Application for AppState {
|
impl Application for AppState {
|
||||||
type Message = AppMessages;
|
type Message = AppMessages;
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) {
|
fn update(&mut self, message: Self::Message) -> bool {
|
||||||
match message {
|
match message {
|
||||||
AppMessages::Update(new_data) => {
|
AppMessages::Update(new_data) => {
|
||||||
self.data_collection.eat_data(new_data);
|
self.data_collection.eat_data(new_data);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
AppMessages::OpenHelp => {
|
AppMessages::OpenHelp => {
|
||||||
self.set_current_screen(CurrentScreen::Help);
|
self.set_current_screen(CurrentScreen::Help);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
AppMessages::KillProcess { to_kill } => {}
|
AppMessages::ConfirmKillProcess { to_kill } => true,
|
||||||
|
AppMessages::KillProcess { to_kill, signal } => true,
|
||||||
AppMessages::ToggleFreeze => {
|
AppMessages::ToggleFreeze => {
|
||||||
self.frozen_state.toggle(&self.data_collection);
|
self.frozen_state.toggle(&self.data_collection);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
AppMessages::Clean => {
|
AppMessages::Clean => {
|
||||||
self.data_collection
|
self.data_collection
|
||||||
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
||||||
|
false
|
||||||
}
|
}
|
||||||
AppMessages::Quit => {
|
AppMessages::Quit => {
|
||||||
self.terminator.store(true, SeqCst);
|
self.terminator.store(true, SeqCst);
|
||||||
|
false
|
||||||
}
|
}
|
||||||
AppMessages::Reset => {
|
AppMessages::Reset => {
|
||||||
// FIXME: Reset
|
// FIXME: Reset
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,10 +257,21 @@ impl Application for AppState {
|
|||||||
use crate::tuine::StatefulComponent;
|
use crate::tuine::StatefulComponent;
|
||||||
use crate::tuine::{TempTable, TextTable, TextTableProps};
|
use crate::tuine::{TempTable, TextTable, TextTableProps};
|
||||||
|
|
||||||
|
let data = match &self.frozen_state {
|
||||||
|
FrozenState::NotFrozen => &self.data_collection,
|
||||||
|
FrozenState::Frozen(frozen_data_collection) => &frozen_data_collection,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut converted_data = ConvertedData::default();
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_flex_child(
|
.with_flex_child(
|
||||||
Flex::row_with_children(vec![
|
Flex::row_with_children(vec![
|
||||||
FlexElement::new(TempTable::build(ctx)),
|
FlexElement::new(TempTable::build(
|
||||||
|
ctx,
|
||||||
|
&self.painter,
|
||||||
|
converted_data.temp_table(data, self.app_config_fields.temperature_type),
|
||||||
|
)),
|
||||||
FlexElement::new(TextTable::build(
|
FlexElement::new(TextTable::build(
|
||||||
ctx,
|
ctx,
|
||||||
TextTableProps::new(vec!["D", "E", "F"]),
|
TextTableProps::new(vec!["D", "E", "F"]),
|
||||||
|
@ -175,6 +175,7 @@ impl Default for DataCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Just rip this out, store only stringified data...?
|
||||||
impl DataCollection {
|
impl DataCollection {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.timed_data_vec = Default::default();
|
self.timed_data_vec = Default::default();
|
||||||
|
@ -23,7 +23,7 @@ pub struct TempHarvest {
|
|||||||
pub temperature: f32,
|
pub temperature: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum TemperatureType {
|
pub enum TemperatureType {
|
||||||
Celsius,
|
Celsius,
|
||||||
Kelvin,
|
Kelvin,
|
||||||
|
@ -7,6 +7,47 @@ use crate::{app::AxisScaling, units::data_units::DataUnit};
|
|||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Stores converted data, and caches results.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ConvertedData {
|
||||||
|
temp_table: Option<Vec<Vec<Cow<'static, str>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConvertedData {
|
||||||
|
pub fn temp_table(
|
||||||
|
&mut self, data: &DataCollection, temp_type: TemperatureType,
|
||||||
|
) -> Vec<Vec<Cow<'static, str>>> {
|
||||||
|
match &self.temp_table {
|
||||||
|
Some(temp_table) => temp_table.clone(),
|
||||||
|
None => {
|
||||||
|
let temp_table = if data.temp_harvest.is_empty() {
|
||||||
|
vec![vec!["No Sensors Found".into(), "".into()]]
|
||||||
|
} else {
|
||||||
|
let unit = match temp_type {
|
||||||
|
data_harvester::temperature::TemperatureType::Celsius => "°C",
|
||||||
|
data_harvester::temperature::TemperatureType::Kelvin => "K",
|
||||||
|
data_harvester::temperature::TemperatureType::Fahrenheit => "°F",
|
||||||
|
};
|
||||||
|
|
||||||
|
data.temp_harvest
|
||||||
|
.iter()
|
||||||
|
.map(|temp_harvest| {
|
||||||
|
let val = temp_harvest.temperature.ceil().to_string();
|
||||||
|
vec![
|
||||||
|
temp_harvest.name.clone().into(),
|
||||||
|
format!("{}{}", val, unit).into(),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.temp_table = Some(temp_table.clone());
|
||||||
|
temp_table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Point is of time, data
|
/// Point is of time, data
|
||||||
type Point = (f64, f64);
|
type Point = (f64, f64);
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ pub type CrosstermBackend = tui::backend::CrosstermBackend<std::io::Stdout>;
|
|||||||
pub trait Application: Sized {
|
pub trait Application: Sized {
|
||||||
type Message: Debug;
|
type Message: Debug;
|
||||||
|
|
||||||
/// Determines how to handle a given message.
|
/// Determines how to handle a given message, and returns `true` if this update should trigger a redraw.
|
||||||
fn update(&mut self, message: Self::Message);
|
fn update(&mut self, message: Self::Message) -> bool;
|
||||||
|
|
||||||
/// Returns whether to stop the application. Defaults to
|
/// Returns whether to stop the application. Defaults to
|
||||||
/// always returning false.
|
/// always returning false.
|
||||||
|
@ -9,8 +9,7 @@ use crate::tuine::{
|
|||||||
/// A set of styles for a [`Block`].
|
/// A set of styles for a [`Block`].
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct StyleSheet {
|
pub struct StyleSheet {
|
||||||
text: Style,
|
pub border: Style,
|
||||||
border: Style,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional
|
/// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional
|
||||||
@ -47,6 +46,11 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn style(mut self, style: StyleSheet) -> Self {
|
||||||
|
self.style_sheet = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn inner_rect(&self, original: Rect) -> Rect {
|
fn inner_rect(&self, original: Rect) -> Rect {
|
||||||
let mut inner = original;
|
let mut inner = original;
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ mod table_scroll_state;
|
|||||||
use self::table_scroll_state::ScrollState;
|
use self::table_scroll_state::ScrollState;
|
||||||
|
|
||||||
pub mod data_row;
|
pub mod data_row;
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
pub use data_row::DataRow;
|
pub use data_row::DataRow;
|
||||||
|
|
||||||
pub mod data_cell;
|
pub mod data_cell;
|
||||||
pub use data_cell::DataCell;
|
pub use data_cell::DataCell;
|
||||||
|
|
||||||
pub mod sort_type;
|
pub mod sort_type;
|
||||||
|
|
||||||
pub use sort_type::SortType;
|
pub use sort_type::SortType;
|
||||||
|
|
||||||
pub mod props;
|
pub mod props;
|
||||||
@ -35,9 +37,9 @@ use crate::{
|
|||||||
/// A set of styles for a [`TextTable`].
|
/// A set of styles for a [`TextTable`].
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct StyleSheet {
|
pub struct StyleSheet {
|
||||||
text: Style,
|
pub text: Style,
|
||||||
selected_text: Style,
|
pub selected_text: Style,
|
||||||
table_header: Style,
|
pub table_header: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Default)]
|
#[derive(PartialEq, Default)]
|
||||||
@ -108,6 +110,71 @@ impl<Message> TextTable<Message> {
|
|||||||
|
|
||||||
self.column_widths = column_widths;
|
self.column_widths = column_widths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_sort_column(&mut self, state: &mut TextTableState, x: u16) -> Status {
|
||||||
|
match state.sort {
|
||||||
|
SortType::Unsortable => Status::Ignored,
|
||||||
|
SortType::Ascending(column) | SortType::Descending(column) => {
|
||||||
|
let mut cursor = 0;
|
||||||
|
for (selected_column, width) in self.column_widths.iter().enumerate() {
|
||||||
|
if x >= cursor && x <= cursor + width {
|
||||||
|
match state.sort {
|
||||||
|
SortType::Ascending(_) => {
|
||||||
|
if selected_column == column {
|
||||||
|
// FIXME: This should handle default sorting orders...
|
||||||
|
state.sort = SortType::Descending(selected_column);
|
||||||
|
} else {
|
||||||
|
state.sort = SortType::Ascending(selected_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SortType::Descending(_) => {
|
||||||
|
if selected_column == column {
|
||||||
|
// FIXME: This should handle default sorting orders...
|
||||||
|
state.sort = SortType::Ascending(selected_column);
|
||||||
|
} else {
|
||||||
|
state.sort = SortType::Descending(selected_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SortType::Unsortable => unreachable!(), // Should be impossible by above check.
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status::Captured;
|
||||||
|
} else {
|
||||||
|
cursor += width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Status::Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_page_up(&mut self, state: &mut TextTableState, rect: Rect) -> Status {
|
||||||
|
let height = rect.height.saturating_sub(self.table_gap + 1);
|
||||||
|
state.scroll.move_up(height.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_page_down(&mut self, state: &mut TextTableState, rect: Rect) -> Status {
|
||||||
|
let height = rect.height.saturating_sub(self.table_gap + 1);
|
||||||
|
state.scroll.move_down(height.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_down(
|
||||||
|
&mut self, state: &mut TextTableState, messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
|
let status = state.scroll.move_down(1);
|
||||||
|
if let Some(on_select) = &self.on_select {
|
||||||
|
messages.push(on_select(state.scroll.current_index()));
|
||||||
|
}
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_up(&mut self, state: &mut TextTableState, messages: &mut Vec<Message>) -> Status {
|
||||||
|
let status = state.scroll.move_up(1);
|
||||||
|
if let Some(on_select) = &self.on_select {
|
||||||
|
messages.push(on_select(state.scroll.current_index()));
|
||||||
|
}
|
||||||
|
status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
||||||
@ -190,7 +257,29 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Now build up our headers...
|
// Now build up our headers...
|
||||||
let header = Row::new(self.columns.iter().map(|column| column.name.clone()))
|
let header = match state.sort {
|
||||||
|
SortType::Unsortable => Row::new(self.columns.iter().map(|column| column.name.clone())),
|
||||||
|
SortType::Ascending(sort_column) => {
|
||||||
|
Row::new(self.columns.iter().enumerate().map(|(index, column)| {
|
||||||
|
const UP_ARROW: &str = "▲";
|
||||||
|
if index == sort_column {
|
||||||
|
format!("{}{}", column.name, UP_ARROW).into()
|
||||||
|
} else {
|
||||||
|
column.name.clone()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
SortType::Descending(sort_column) => {
|
||||||
|
Row::new(self.columns.iter().enumerate().map(|(index, column)| {
|
||||||
|
const DOWN_ARROW: &str = "▼";
|
||||||
|
if index == sort_column {
|
||||||
|
format!("{}{}", column.name, DOWN_ARROW).into()
|
||||||
|
} else {
|
||||||
|
column.name.clone()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
.style(self.style_sheet.table_header)
|
.style(self.style_sheet.table_header)
|
||||||
.bottom_margin(self.table_gap);
|
.bottom_margin(self.table_gap);
|
||||||
|
|
||||||
@ -219,6 +308,10 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
Event::Keyboard(key_event) => {
|
Event::Keyboard(key_event) => {
|
||||||
if key_event.modifiers.is_empty() {
|
if key_event.modifiers.is_empty() {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
|
KeyCode::PageUp => self.on_page_up(state, rect),
|
||||||
|
KeyCode::PageDown => self.on_page_down(state, rect),
|
||||||
|
KeyCode::Up => self.scroll_up(state, messages),
|
||||||
|
KeyCode::Down => self.scroll_down(state, messages),
|
||||||
_ => Status::Ignored,
|
_ => Status::Ignored,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -232,52 +325,7 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
let y = mouse_event.row - rect.top();
|
let y = mouse_event.row - rect.top();
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
let x = mouse_event.column - rect.left();
|
let x = mouse_event.column - rect.left();
|
||||||
match state.sort {
|
self.update_sort_column(state, x)
|
||||||
SortType::Unsortable => Status::Ignored,
|
|
||||||
SortType::Ascending(column) | SortType::Descending(column) => {
|
|
||||||
let mut cursor = 0;
|
|
||||||
for (selected_column, width) in
|
|
||||||
self.column_widths.iter().enumerate()
|
|
||||||
{
|
|
||||||
let end = cursor + width;
|
|
||||||
|
|
||||||
if x >= cursor && x <= end {
|
|
||||||
match state.sort {
|
|
||||||
SortType::Ascending(_) => {
|
|
||||||
if selected_column == column {
|
|
||||||
// FIXME: This should handle default sorting orders...
|
|
||||||
state.sort = SortType::Descending(
|
|
||||||
selected_column,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
state.sort = SortType::Ascending(
|
|
||||||
selected_column,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SortType::Descending(_) => {
|
|
||||||
if selected_column == column {
|
|
||||||
// FIXME: This should handle default sorting orders...
|
|
||||||
state.sort = SortType::Ascending(
|
|
||||||
selected_column,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
state.sort = SortType::Descending(
|
|
||||||
selected_column,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SortType::Unsortable => unreachable!(), // Should be impossible by above check.
|
|
||||||
}
|
|
||||||
|
|
||||||
return Status::Captured;
|
|
||||||
} else {
|
|
||||||
cursor += width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Status::Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if y > self.table_gap {
|
} else if y > self.table_gap {
|
||||||
let visual_index = usize::from(y - self.table_gap);
|
let visual_index = usize::from(y - self.table_gap);
|
||||||
match state.scroll.set_visual_index(visual_index) {
|
match state.scroll.set_visual_index(visual_index) {
|
||||||
@ -297,20 +345,8 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
Status::Ignored
|
Status::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MouseEventKind::ScrollDown => {
|
MouseEventKind::ScrollDown => self.scroll_down(state, messages),
|
||||||
let status = state.scroll.move_down(1);
|
MouseEventKind::ScrollUp => self.scroll_up(state, messages),
|
||||||
if let Some(on_select) = &self.on_select {
|
|
||||||
messages.push(on_select(state.scroll.current_index()));
|
|
||||||
}
|
|
||||||
status
|
|
||||||
}
|
|
||||||
MouseEventKind::ScrollUp => {
|
|
||||||
let status = state.scroll.move_up(1);
|
|
||||||
if let Some(on_select) = &self.on_select {
|
|
||||||
messages.push(on_select(state.scroll.current_index()));
|
|
||||||
}
|
|
||||||
status
|
|
||||||
}
|
|
||||||
_ => Status::Ignored,
|
_ => Status::Ignored,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -94,6 +94,12 @@ impl<Message> TextTableProps<Message> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the style for the entry.
|
||||||
|
pub fn style(mut self, style: StyleSheet) -> Self {
|
||||||
|
self.style_sheet = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn try_sort_data(&mut self, sort_type: SortType) {
|
pub(crate) fn try_sort_data(&mut self, sort_type: SortType) {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::tuine::{State, ViewContext};
|
use crate::tuine::{State, StateContext, ViewContext};
|
||||||
|
|
||||||
use super::TmpComponent;
|
use super::TmpComponent;
|
||||||
|
|
||||||
|
0
src/tuine/component/widget/battery_table.rs
Normal file
0
src/tuine/component/widget/battery_table.rs
Normal file
0
src/tuine/component/widget/cpu_graph.rs
Normal file
0
src/tuine/component/widget/cpu_graph.rs
Normal file
0
src/tuine/component/widget/cpu_simple.rs
Normal file
0
src/tuine/component/widget/cpu_simple.rs
Normal file
0
src/tuine/component/widget/disk_table.rs
Normal file
0
src/tuine/component/widget/disk_table.rs
Normal file
0
src/tuine/component/widget/mem_graph.rs
Normal file
0
src/tuine/component/widget/mem_graph.rs
Normal file
0
src/tuine/component/widget/mem_simple.rs
Normal file
0
src/tuine/component/widget/mem_simple.rs
Normal file
@ -1,2 +1,32 @@
|
|||||||
|
pub mod simple_table;
|
||||||
|
pub use simple_table::*;
|
||||||
|
|
||||||
|
pub mod cpu_graph;
|
||||||
|
pub use cpu_graph::*;
|
||||||
|
|
||||||
|
pub mod disk_table;
|
||||||
|
pub use disk_table::*;
|
||||||
|
|
||||||
|
pub mod mem_graph;
|
||||||
|
pub use mem_graph::*;
|
||||||
|
|
||||||
|
pub mod net_graph;
|
||||||
|
pub use net_graph::*;
|
||||||
|
|
||||||
|
pub mod process_table;
|
||||||
|
pub use process_table::*;
|
||||||
|
|
||||||
pub mod temp_table;
|
pub mod temp_table;
|
||||||
pub use temp_table::*;
|
pub use temp_table::*;
|
||||||
|
|
||||||
|
pub mod battery_table;
|
||||||
|
pub use battery_table::*;
|
||||||
|
|
||||||
|
pub mod cpu_simple;
|
||||||
|
pub use cpu_simple::*;
|
||||||
|
|
||||||
|
pub mod mem_simple;
|
||||||
|
pub use mem_simple::*;
|
||||||
|
|
||||||
|
pub mod net_simple;
|
||||||
|
pub use net_simple::*;
|
||||||
|
0
src/tuine/component/widget/net_graph.rs
Normal file
0
src/tuine/component/widget/net_graph.rs
Normal file
0
src/tuine/component/widget/net_simple.rs
Normal file
0
src/tuine/component/widget/net_simple.rs
Normal file
0
src/tuine/component/widget/process_table.rs
Normal file
0
src/tuine/component/widget/process_table.rs
Normal file
72
src/tuine/component/widget/simple_table.rs
Normal file
72
src/tuine/component/widget/simple_table.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
use tui::style::Style;
|
||||||
|
|
||||||
|
use crate::tuine::{
|
||||||
|
self, block,
|
||||||
|
text_table::{self, DataRow, SortType, TextTableProps},
|
||||||
|
Block, Event, Shortcut, StatefulComponent, Status, TextTable, TmpComponent, ViewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A set of styles for a [`SimpleTable`].
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct StyleSheet {
|
||||||
|
pub text: Style,
|
||||||
|
pub selected_text: Style,
|
||||||
|
pub table_header: Style,
|
||||||
|
pub border: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`SimpleTable`] is a wrapper around a [`TextTable`] with basic shortcut support already added for:
|
||||||
|
/// - Skipping to the start/end of the table
|
||||||
|
/// - Scrolling up/down by a page
|
||||||
|
/// - Configurable sorting options
|
||||||
|
pub struct SimpleTable<Message> {
|
||||||
|
inner: Block<Message, Shortcut<Message, TextTable<Message>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> SimpleTable<Message> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn build<C: Into<std::borrow::Cow<'static, str>>, R: Into<DataRow>>(
|
||||||
|
ctx: &mut ViewContext<'_>, style: StyleSheet, columns: Vec<C>, data: Vec<R>,
|
||||||
|
) -> Self {
|
||||||
|
let shortcut = Shortcut::with_child(TextTable::build(
|
||||||
|
ctx,
|
||||||
|
TextTableProps::new(columns)
|
||||||
|
.rows(data)
|
||||||
|
.default_sort(SortType::Ascending(1))
|
||||||
|
.style(text_table::StyleSheet {
|
||||||
|
text: style.text,
|
||||||
|
selected_text: style.selected_text,
|
||||||
|
table_header: style.table_header,
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: Block::with_child(shortcut).style(block::StyleSheet {
|
||||||
|
border: style.border,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> TmpComponent<Message> for SimpleTable<Message> {
|
||||||
|
fn draw<Backend>(
|
||||||
|
&mut self, state_ctx: &mut tuine::StateContext<'_>, draw_ctx: &tuine::DrawContext<'_>,
|
||||||
|
frame: &mut tui::Frame<'_, Backend>,
|
||||||
|
) where
|
||||||
|
Backend: tui::backend::Backend,
|
||||||
|
{
|
||||||
|
self.inner.draw(state_ctx, draw_ctx, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self, state_ctx: &mut tuine::StateContext<'_>, draw_ctx: &tuine::DrawContext<'_>,
|
||||||
|
event: tuine::Event, messages: &mut Vec<Message>,
|
||||||
|
) -> tuine::Status {
|
||||||
|
self.inner.on_event(state_ctx, draw_ctx, event, messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self, bounds: tuine::Bounds, node: &mut tuine::LayoutNode) -> tuine::Size {
|
||||||
|
self.inner.layout(bounds, node)
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,41 @@
|
|||||||
use crate::tuine::{
|
use crate::{
|
||||||
text_table::{DataRow, SortType, TextTableProps},
|
canvas::Painter,
|
||||||
Block, Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext,
|
tuine::{
|
||||||
|
Bounds, DataRow, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status,
|
||||||
|
TmpComponent, ViewContext,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A [`TempTable`] is a text table that is meant to display temperature data.
|
use super::simple_table;
|
||||||
|
|
||||||
|
/// A [`TempTable`] is a table displaying temperature data.
|
||||||
|
///
|
||||||
|
/// It wraps a [`SimpleTable`], with set columns and manages extracting data and styling.
|
||||||
pub struct TempTable<Message> {
|
pub struct TempTable<Message> {
|
||||||
inner: Block<Message, Shortcut<Message, TextTable<Message>>>,
|
inner: SimpleTable<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> TempTable<Message> {
|
impl<Message> TempTable<Message> {
|
||||||
#[track_caller]
|
pub fn build<R: Into<DataRow>>(
|
||||||
pub fn build(ctx: &mut ViewContext<'_>) -> Self {
|
ctx: &mut ViewContext<'_>, painter: &Painter, data: Vec<R>,
|
||||||
|
) -> Self {
|
||||||
|
let style = simple_table::StyleSheet {
|
||||||
|
text: painter.colours.text_style,
|
||||||
|
selected_text: painter.colours.currently_selected_text_style,
|
||||||
|
table_header: painter.colours.table_header_style,
|
||||||
|
border: painter.colours.border_style,
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner: Block::with_child(Shortcut::with_child(TextTable::build(
|
inner: SimpleTable::build(ctx, style, vec!["Sensor", "Temp"], data),
|
||||||
ctx,
|
|
||||||
TextTableProps::new(vec!["Sensor", "Temp"])
|
|
||||||
.rows(vec![
|
|
||||||
DataRow::default().cell("A").cell(2),
|
|
||||||
DataRow::default().cell("B").cell(3),
|
|
||||||
DataRow::default().cell("C").cell(1),
|
|
||||||
])
|
|
||||||
.default_sort(SortType::Ascending(1)),
|
|
||||||
))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> TmpComponent<Message> for TempTable<Message> {
|
impl<Message> TmpComponent<Message> for TempTable<Message> {
|
||||||
fn draw<Backend>(
|
fn draw<Backend>(
|
||||||
&mut self, state_ctx: &mut crate::tuine::StateContext<'_>,
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
|
||||||
draw_ctx: &crate::tuine::DrawContext<'_>, frame: &mut tui::Frame<'_, Backend>,
|
frame: &mut tui::Frame<'_, Backend>,
|
||||||
) where
|
) where
|
||||||
Backend: tui::backend::Backend,
|
Backend: tui::backend::Backend,
|
||||||
{
|
{
|
||||||
@ -37,16 +43,13 @@ impl<Message> TmpComponent<Message> for TempTable<Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
&mut self, state_ctx: &mut crate::tuine::StateContext<'_>,
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
|
||||||
draw_ctx: &crate::tuine::DrawContext<'_>, event: crate::tuine::Event,
|
event: crate::tuine::Event, messages: &mut Vec<Message>,
|
||||||
messages: &mut Vec<Message>,
|
) -> Status {
|
||||||
) -> crate::tuine::Status {
|
|
||||||
self.inner.on_event(state_ctx, draw_ctx, event, messages)
|
self.inner.on_event(state_ctx, draw_ctx, event, messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
|
||||||
&self, bounds: crate::tuine::Bounds, node: &mut crate::tuine::LayoutNode,
|
|
||||||
) -> crate::tuine::Size {
|
|
||||||
self.inner.layout(bounds, node)
|
self.inner.layout(bounds, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use tui::Frame;
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Block, Bounds, Carousel, Container, DrawContext, Empty, Event, Flex, LayoutNode, Shortcut,
|
Block, Bounds, Carousel, Container, DrawContext, Empty, Event, Flex, LayoutNode, Shortcut,
|
||||||
Size, StateContext, Status, TempTable, TextTable, TmpComponent,
|
SimpleTable, Size, StateContext, Status, TempTable, TextTable, TmpComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An [`Element`] is an instantiated [`Component`].
|
/// An [`Element`] is an instantiated [`Component`].
|
||||||
@ -19,5 +19,6 @@ where
|
|||||||
Shortcut(Shortcut<Message, C>),
|
Shortcut(Shortcut<Message, C>),
|
||||||
TextTable(TextTable<Message>),
|
TextTable(TextTable<Message>),
|
||||||
Empty,
|
Empty,
|
||||||
|
SimpleTable(SimpleTable<Message>),
|
||||||
TempTable(TempTable<Message>),
|
TempTable(TempTable<Message>),
|
||||||
}
|
}
|
||||||
|
@ -41,27 +41,22 @@ where
|
|||||||
if let Ok(event) = receiver.recv() {
|
if let Ok(event) = receiver.recv() {
|
||||||
match event {
|
match event {
|
||||||
RuntimeEvent::UserInterface(event) => {
|
RuntimeEvent::UserInterface(event) => {
|
||||||
match on_event(
|
if on_event(
|
||||||
&mut application,
|
&mut application,
|
||||||
&mut user_interface,
|
&mut user_interface,
|
||||||
&mut app_data,
|
&mut app_data,
|
||||||
&mut layout,
|
&mut layout,
|
||||||
event,
|
event,
|
||||||
) {
|
) {
|
||||||
Status::Captured => {
|
|
||||||
// Hmm... is this really needed? Or is it fine to redraw once then do the termination check?
|
|
||||||
if application.is_terminated() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
user_interface = new_user_interface(&mut application, &mut app_data);
|
user_interface = new_user_interface(&mut application, &mut app_data);
|
||||||
draw(&mut user_interface, terminal, &mut app_data, &mut layout)?;
|
draw(&mut user_interface, terminal, &mut app_data, &mut layout)?;
|
||||||
}
|
}
|
||||||
Status::Ignored => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
RuntimeEvent::Custom(message) => {
|
RuntimeEvent::Custom(message) => {
|
||||||
application.update(message);
|
if application.update(message) {
|
||||||
|
user_interface = new_user_interface(&mut application, &mut app_data);
|
||||||
|
draw(&mut user_interface, terminal, &mut app_data, &mut layout)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RuntimeEvent::Resize {
|
RuntimeEvent::Resize {
|
||||||
width: _,
|
width: _,
|
||||||
@ -86,7 +81,7 @@ where
|
|||||||
fn on_event<A>(
|
fn on_event<A>(
|
||||||
application: &mut A, user_interface: &mut Element<A::Message>, app_data: &mut AppData,
|
application: &mut A, user_interface: &mut Element<A::Message>, app_data: &mut AppData,
|
||||||
layout: &mut LayoutNode, event: Event,
|
layout: &mut LayoutNode, event: Event,
|
||||||
) -> Status
|
) -> bool
|
||||||
where
|
where
|
||||||
A: Application + 'static,
|
A: Application + 'static,
|
||||||
{
|
{
|
||||||
@ -103,12 +98,18 @@ where
|
|||||||
Status::Ignored => application.global_event_handler(event, &mut messages),
|
Status::Ignored => application.global_event_handler(event, &mut messages),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut should_redraw = match event_handled {
|
||||||
|
Status::Captured => true,
|
||||||
|
Status::Ignored => false,
|
||||||
|
};
|
||||||
|
|
||||||
for msg in messages {
|
for msg in messages {
|
||||||
debug!("Message: {:?}", msg); // FIXME: Remove this debug line!
|
debug!("Message: {:?}", msg); // FIXME: Remove this debug line!
|
||||||
application.update(msg);
|
let msg_result = application.update(msg);
|
||||||
|
should_redraw = should_redraw || msg_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_handled
|
should_redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Element`] representing the root of the user interface.
|
/// Creates a new [`Element`] representing the root of the user interface.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user