Merge pull request #131 from ClementTsang/battery_flag

Add battery flag, fix bug, refactor
This commit is contained in:
Clement Tsang 2020-04-27 16:59:07 -04:00 committed by GitHub
commit de3a1fb7c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 896 additions and 702 deletions

View file

@ -9,11 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Features
- [#58](https://github.com/ClementTsang/bottom/issues/58): I/O stats per process
- [#58](https://github.com/ClementTsang/bottom/issues/58): I/O stats per process.
- [#55](https://github.com/ClementTsang/bottom/issues/55): Battery monitoring widget
- [#55](https://github.com/ClementTsang/bottom/issues/55): Battery monitoring widget.
- [#114](https://github.com/ClementTsang/bottom/pull/114): Process state per process
- [#114](https://github.com/ClementTsang/bottom/pull/114): Process state per process.
### Changes
@ -49,6 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed bug where a single empty row as a layout would crash without a proper warning.
The behaviour now errors out with a more helpful message.
- Fixed bug where empty widgets in layout would cause widget movement to not work properly when moving vertically.
### Development changes
- Switch to stateful widget style for tables.

View file

@ -143,20 +143,21 @@ Run using `btm`.
-s, --show_disabled_data Shows disabled CPU entries in the CPU legend
-b, --basic Enables basic mode, removing charts and condensing data
--autohide_time Automatically hide the time scaling in graphs after being shown for a brief moment when
zoomed in/out. If time is disabled via --hide_time then this will have no effect.
zoomed in/out. If time is disabled via --hide_time then this will have no effect
--use_old_network_legend Use the older (pre-0.4) network legend which is separate from the network chart
--hide_table_gap Hides the spacing between table headers and data
--battery Displays the battery widget for default and basic layouts
```
### Options
```
-r, --rate <MS> Set the refresh rate in milliseconds [default: 1000]
-C, --config <PATH> Use the specified config file; if it does not exist it is automatically created
-C, --config <PATH> Use the specified config file; if it does not exist it is automatically created [default: see section on config files]
-t, --default_time_value <MS> Sets the default time interval for charts in milliseconds [default: 60000]
-d, --time_delta <MS> Sets the default amount each zoom in/out action changes by in milliseconds [default: 15000]
--default_widget_count <COUNT> Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1.
--default_widget_type <TYPE> The default widget type to select by default.
--default_widget_count <COUNT> Which number of the selected widget type to select, from left to right, top to bottom [default: 1]
--default_widget_type <TYPE> The default widget type to select by default [default: "process"]
```
### Keybindings
@ -418,8 +419,8 @@ and get the following CPU donut:
### Battery
You can get battery statistics (charge, time to fill/discharge, consumption in watts, and battery health) via the battery widget.
Since this is only useful for devices like laptops, it is off by default. Currently, the only way to use it is to set it
as a widget via [layouts](#layout).
Since this is only useful for devices like laptops, it is off by default. You can either enable the widget in the default layout via the `--battery` flag, or by specifying the widget in a [layout](#layout).
So with this slightly silly layout:

View file

@ -3,13 +3,12 @@ use std::{cmp::max, collections::HashMap, time::Instant};
use unicode_segmentation::GraphemeCursor;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use tui::widgets::TableState;
use typed_builder::*;
use data_farmer::*;
use data_harvester::{processes, temperature};
use layout_manager::*;
pub use states::*;
use crate::{
canvas, constants,
@ -20,60 +19,10 @@ pub mod data_farmer;
pub mod data_harvester;
pub mod layout_manager;
mod process_killer;
pub mod states;
const MAX_SEARCH_LENGTH: usize = 200;
#[derive(Debug)]
pub enum ScrollDirection {
// UP means scrolling up --- this usually DECREMENTS
UP,
// DOWN means scrolling down --- this usually INCREMENTS
DOWN,
}
impl Default for ScrollDirection {
fn default() -> Self {
ScrollDirection::DOWN
}
}
#[derive(Debug)]
pub enum CursorDirection {
LEFT,
RIGHT,
}
/// AppScrollWidgetState deals with fields for a scrollable app's current state.
#[derive(Default)]
pub struct AppScrollWidgetState {
pub current_scroll_position: u64,
pub previous_scroll_position: u64,
pub scroll_direction: ScrollDirection,
pub table_state: TableState,
}
#[derive(Default)]
pub struct AppDeleteDialogState {
pub is_showing_dd: bool,
pub is_on_yes: bool, // Defaults to "No"
}
pub struct AppHelpDialogState {
pub is_showing_help: bool,
pub scroll_state: ParagraphScrollState,
pub index_shortcuts: Vec<u16>,
}
impl Default for AppHelpDialogState {
fn default() -> Self {
AppHelpDialogState {
is_showing_help: false,
scroll_state: ParagraphScrollState::default(),
index_shortcuts: vec![0; constants::HELP_TEXT.len()],
}
}
}
/// AppConfigFields is meant to cover basic fields that would normally be set
/// by config files or launch options.
pub struct AppConfigFields {
@ -93,396 +42,6 @@ pub struct AppConfigFields {
pub table_gap: u16,
}
/// AppSearchState deals with generic searching (I might do this in the future).
pub struct AppSearchState {
pub is_enabled: bool,
pub current_search_query: String,
pub current_regex: Option<std::result::Result<regex::Regex, regex::Error>>,
pub is_blank_search: bool,
pub is_invalid_search: bool,
pub grapheme_cursor: GraphemeCursor,
pub cursor_direction: CursorDirection,
pub cursor_bar: usize,
/// This represents the position in terms of CHARACTERS, not graphemes
pub char_cursor_position: usize,
}
impl Default for AppSearchState {
fn default() -> Self {
AppSearchState {
is_enabled: false,
current_search_query: String::default(),
current_regex: None,
is_invalid_search: false,
is_blank_search: true,
grapheme_cursor: GraphemeCursor::new(0, 0, true),
cursor_direction: CursorDirection::RIGHT,
cursor_bar: 0,
char_cursor_position: 0,
}
}
}
impl AppSearchState {
/// Returns a reset but still enabled app search state
pub fn reset(&mut self) {
*self = AppSearchState {
is_enabled: self.is_enabled,
..AppSearchState::default()
}
}
pub fn is_invalid_or_blank_search(&self) -> bool {
self.is_blank_search || self.is_invalid_search
}
}
/// ProcessSearchState only deals with process' search's current settings and state.
pub struct ProcessSearchState {
pub search_state: AppSearchState,
pub is_searching_with_pid: bool,
pub is_ignoring_case: bool,
pub is_searching_whole_word: bool,
pub is_searching_with_regex: bool,
}
impl Default for ProcessSearchState {
fn default() -> Self {
ProcessSearchState {
search_state: AppSearchState::default(),
is_searching_with_pid: false,
is_ignoring_case: true,
is_searching_whole_word: false,
is_searching_with_regex: false,
}
}
}
impl ProcessSearchState {
pub fn search_toggle_ignore_case(&mut self) {
self.is_ignoring_case = !self.is_ignoring_case;
}
pub fn search_toggle_whole_word(&mut self) {
self.is_searching_whole_word = !self.is_searching_whole_word;
}
pub fn search_toggle_regex(&mut self) {
self.is_searching_with_regex = !self.is_searching_with_regex;
}
}
pub struct ProcWidgetState {
pub process_search_state: ProcessSearchState,
pub is_grouped: bool,
pub scroll_state: AppScrollWidgetState,
pub process_sorting_type: processes::ProcessSorting,
pub process_sorting_reverse: bool,
}
impl ProcWidgetState {
pub fn init(
is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool,
) -> Self {
let mut process_search_state = ProcessSearchState::default();
if is_case_sensitive {
// By default it's off
process_search_state.search_toggle_ignore_case();
}
if is_match_whole_word {
process_search_state.search_toggle_whole_word();
}
if is_use_regex {
process_search_state.search_toggle_regex();
}
ProcWidgetState {
process_search_state,
is_grouped,
scroll_state: AppScrollWidgetState::default(),
process_sorting_type: processes::ProcessSorting::CPU,
process_sorting_reverse: true,
}
}
pub fn get_cursor_position(&self) -> usize {
self.process_search_state
.search_state
.grapheme_cursor
.cur_cursor()
}
pub fn get_char_cursor_position(&self) -> usize {
self.process_search_state.search_state.char_cursor_position
}
pub fn is_search_enabled(&self) -> bool {
self.process_search_state.search_state.is_enabled
}
pub fn get_current_search_query(&self) -> &String {
&self.process_search_state.search_state.current_search_query
}
pub fn update_regex(&mut self) {
if self
.process_search_state
.search_state
.current_search_query
.is_empty()
{
self.process_search_state.search_state.is_invalid_search = false;
self.process_search_state.search_state.is_blank_search = true;
} else {
let regex_string = &self.process_search_state.search_state.current_search_query;
let escaped_regex: String;
let final_regex_string = &format!(
"{}{}{}{}",
if self.process_search_state.is_searching_whole_word {
"^"
} else {
""
},
if self.process_search_state.is_ignoring_case {
"(?i)"
} else {
""
},
if !self.process_search_state.is_searching_with_regex {
escaped_regex = regex::escape(regex_string);
&escaped_regex
} else {
regex_string
},
if self.process_search_state.is_searching_whole_word {
"$"
} else {
""
},
);
let new_regex = regex::Regex::new(final_regex_string);
self.process_search_state.search_state.is_blank_search = false;
self.process_search_state.search_state.is_invalid_search = new_regex.is_err();
self.process_search_state.search_state.current_regex = Some(new_regex);
}
self.scroll_state.previous_scroll_position = 0;
self.scroll_state.current_scroll_position = 0;
}
pub fn clear_search(&mut self) {
self.process_search_state.search_state.reset();
}
pub fn search_walk_forward(&mut self, start_position: usize) {
self.process_search_state
.search_state
.grapheme_cursor
.next_boundary(
&self.process_search_state.search_state.current_search_query[start_position..],
start_position,
)
.unwrap();
}
pub fn search_walk_back(&mut self, start_position: usize) {
self.process_search_state
.search_state
.grapheme_cursor
.prev_boundary(
&self.process_search_state.search_state.current_search_query[..start_position],
0,
)
.unwrap();
}
}
pub struct ProcState {
pub widget_states: HashMap<u64, ProcWidgetState>,
pub force_update: Option<u64>,
pub force_update_all: bool,
}
impl ProcState {
pub fn init(widget_states: HashMap<u64, ProcWidgetState>) -> Self {
ProcState {
widget_states,
force_update: None,
force_update_all: false,
}
}
}
pub struct NetWidgetState {
pub current_display_time: u64,
pub autohide_timer: Option<Instant>,
}
impl NetWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
NetWidgetState {
current_display_time,
autohide_timer,
}
}
}
pub struct NetState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, NetWidgetState>,
}
impl NetState {
pub fn init(widget_states: HashMap<u64, NetWidgetState>) -> Self {
NetState {
force_update: None,
widget_states,
}
}
}
pub struct CpuWidgetState {
pub current_display_time: u64,
pub is_legend_hidden: bool,
pub is_showing_tray: bool,
pub core_show_vec: Vec<bool>,
pub num_cpus_shown: usize,
pub autohide_timer: Option<Instant>,
pub scroll_state: AppScrollWidgetState,
}
impl CpuWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
CpuWidgetState {
current_display_time,
is_legend_hidden: false,
is_showing_tray: false,
core_show_vec: Vec::new(),
num_cpus_shown: 0,
autohide_timer,
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct CpuState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, CpuWidgetState>,
pub num_cpus_total: usize,
}
impl CpuState {
pub fn init(widget_states: HashMap<u64, CpuWidgetState>) -> Self {
CpuState {
force_update: None,
widget_states,
num_cpus_total: 0,
}
}
}
pub struct MemWidgetState {
pub current_display_time: u64,
pub autohide_timer: Option<Instant>,
}
impl MemWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
MemWidgetState {
current_display_time,
autohide_timer,
}
}
}
pub struct MemState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, MemWidgetState>,
}
impl MemState {
pub fn init(widget_states: HashMap<u64, MemWidgetState>) -> Self {
MemState {
force_update: None,
widget_states,
}
}
}
pub struct TempWidgetState {
pub scroll_state: AppScrollWidgetState,
}
impl TempWidgetState {
pub fn init() -> Self {
TempWidgetState {
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct TempState {
pub widget_states: HashMap<u64, TempWidgetState>,
}
impl TempState {
pub fn init(widget_states: HashMap<u64, TempWidgetState>) -> Self {
TempState { widget_states }
}
}
pub struct DiskWidgetState {
pub scroll_state: AppScrollWidgetState,
}
impl DiskWidgetState {
pub fn init() -> Self {
DiskWidgetState {
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct DiskState {
pub widget_states: HashMap<u64, DiskWidgetState>,
}
impl DiskState {
pub fn init(widget_states: HashMap<u64, DiskWidgetState>) -> Self {
DiskState { widget_states }
}
}
pub struct BasicTableWidgetState {
// Since this is intended (currently) to only be used for ONE widget, that's
// how it's going to be written. If we want to allow for multiple of these,
// then we can expand outwards with a normal BasicTableState and a hashmap
pub currently_displayed_widget_type: BottomWidgetType,
pub currently_displayed_widget_id: u64,
pub widget_id: i64,
}
#[derive(Default)]
pub struct BatteryWidgetState {
pub currently_selected_battery_index: usize,
}
pub struct BatteryState {
pub widget_states: HashMap<u64, BatteryWidgetState>,
}
impl BatteryState {
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
BatteryState { widget_states }
}
}
#[derive(Default)]
pub struct ParagraphScrollState {
pub current_scroll_index: u16,
pub max_scroll_index: u16,
}
#[derive(TypedBuilder)]
pub struct App {
#[builder(default = false, setter(skip))]
@ -1654,6 +1213,7 @@ impl App {
| BottomWidgetType::Proc
| BottomWidgetType::ProcSearch
| BottomWidgetType::Disk
| BottomWidgetType::Battery
if self.basic_table_widget_state.is_some() =>
{
if let Some(basic_table_widget_state) =
@ -1752,6 +1312,7 @@ impl App {
| BottomWidgetType::Proc
| BottomWidgetType::ProcSearch
| BottomWidgetType::Disk
| BottomWidgetType::Battery
if self.basic_table_widget_state.is_some() =>
{
if let Some(basic_table_widget_state) =

View file

@ -322,73 +322,83 @@ impl BottomLayout {
widget.up_neighbour = Some(current_best_widget_id);
}
}
} else if let Some(next_row_up) = layout_mapping
.range(
..(
row_height_percentage_start,
row_height_percentage_start,
),
)
.next_back()
{
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(col_width_percentage_start, col_width_percentage_end)
};
} else {
for next_row_up in layout_mapping
.range(
..(
row_height_percentage_start,
row_height_percentage_start,
),
)
.rev()
{
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(
col_width_percentage_start,
col_width_percentage_end,
)
};
for col_position in &(next_row_up.1).1 {
if let Some(next_col_row) =
(col_position.1).1.iter().next_back()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0 * candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1 * candidate_difference
/ 100;
for col_position in &(next_row_up.1).1 {
if let Some(next_col_row) =
(col_position.1).1.iter().next_back()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0
* candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1
* candidate_difference
/ 100;
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
) {
let candidate_distance = get_distance(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
if current_best_distance
< candidate_distance
{
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
}
}
}
}
}
}
if current_best_distance > 0 {
widget.up_neighbour = Some(current_best_widget_id);
if current_best_distance > 0 {
widget.up_neighbour = Some(current_best_widget_id);
break;
}
}
}
@ -430,72 +440,80 @@ impl BottomLayout {
widget.down_neighbour = Some(current_best_widget_id);
}
}
} else if let Some(next_row_down) = layout_mapping
.range(
} else {
for next_row_down in layout_mapping.range(
(
row_height_percentage_start + 1,
row_height_percentage_start + 1,
)..,
)
.next()
{
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(col_width_percentage_start, col_width_percentage_end)
};
) {
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(
col_width_percentage_start,
col_width_percentage_end,
)
};
for col_position in &(next_row_down.1).1 {
if let Some(next_col_row) = (col_position.1).1.iter().next()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0 * candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1 * candidate_difference
/ 100;
for col_position in &(next_row_down.1).1 {
if let Some(next_col_row) =
(col_position.1).1.iter().next()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0
* candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1
* candidate_difference
/ 100;
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
) {
let candidate_distance = get_distance(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
if current_best_distance
< candidate_distance
{
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
}
}
}
}
}
}
if current_best_distance > 0 {
widget.down_neighbour = Some(current_best_widget_id);
if current_best_distance > 0 {
widget.down_neighbour = Some(current_best_widget_id);
break;
}
}
}
}
@ -510,7 +528,141 @@ impl BottomLayout {
}
}
pub fn init_basic_default() -> Self {
pub fn init_basic_default(use_battery: bool) -> Self {
let table_widgets = if use_battery {
vec![
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Disk)
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(8))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build()])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(8))
.build()])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Temp)
.widget_id(7)
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(8))
.build()])
.build()])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Battery)
.widget_id(8)
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(4))
.build()])
.build()])
.build(),
]
} else {
vec![
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Disk)
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build()])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Temp)
.widget_id(7)
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(4))
.build()])
.build()])
.build(),
]
};
BottomLayout {
total_row_height_ratio: 3,
rows: vec![
@ -573,119 +725,93 @@ impl BottomLayout {
.build(),
BottomRow::builder()
.canvas_handle_height(true)
.children(vec![
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Disk)
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build()])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Temp)
.widget_id(7)
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(4))
.build()])
.build()])
.build(),
])
.children(table_widgets)
.build(),
],
}
}
pub fn init_default(left_legend: bool) -> Self {
pub fn init_default(left_legend: bool, use_battery: bool) -> Self {
let cpu_layout = if left_legend {
vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(11))
.right_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(12))
.left_neighbour(Some(2))
.right_neighbour(if use_battery { Some(99) } else { None })
.flex_grow(true)
.build(),
]
} else {
vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(11))
.right_neighbour(Some(2))
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(12))
.left_neighbour(Some(1))
.right_neighbour(if use_battery { Some(99) } else { None })
.canvas_handle_width(true)
.build(),
]
};
let first_row_layout = if use_battery {
vec![
BottomCol::builder()
.col_width_ratio(2)
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(cpu_layout)
.build()])
.build(),
BottomCol::builder()
.col_width_ratio(1)
.children(vec![BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Battery)
.widget_id(99)
.down_neighbour(Some(12))
.left_neighbour(Some(if left_legend { 1 } else { 2 }))
.canvas_handle_width(true)
.build()])
.build()])
.build(),
]
} else {
vec![BottomCol::builder()
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(cpu_layout)
.build()])
.build()]
};
BottomLayout {
total_row_height_ratio: 100,
rows: vec![
BottomRow::builder()
.row_height_ratio(30)
.children(vec![BottomCol::builder()
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(if left_legend {
vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(11))
.right_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(12))
.left_neighbour(Some(2))
.flex_grow(true)
.build(),
]
} else {
vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(11))
.right_neighbour(Some(2))
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(12))
.left_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
]
})
.build()])
.build()])
.total_col_ratio(if use_battery { 3 } else { 1 })
.children(first_row_layout)
.build(),
BottomRow::builder()
.total_col_ratio(7)
@ -907,6 +1033,7 @@ impl BottomWidgetType {
Proc => "Processes",
Temp => "Temperature",
Disk => "Disks",
Battery => "Battery",
_ => "",
}
}

448
src/app/states.rs Normal file
View file

@ -0,0 +1,448 @@
use std::{collections::HashMap, time::Instant};
use unicode_segmentation::GraphemeCursor;
use tui::widgets::TableState;
use crate::{app::layout_manager::BottomWidgetType, constants, data_harvester::processes};
#[derive(Debug)]
pub enum ScrollDirection {
// UP means scrolling up --- this usually DECREMENTS
UP,
// DOWN means scrolling down --- this usually INCREMENTS
DOWN,
}
impl Default for ScrollDirection {
fn default() -> Self {
ScrollDirection::DOWN
}
}
#[derive(Debug)]
pub enum CursorDirection {
LEFT,
RIGHT,
}
/// AppScrollWidgetState deals with fields for a scrollable app's current state.
#[derive(Default)]
pub struct AppScrollWidgetState {
pub current_scroll_position: u64,
pub previous_scroll_position: u64,
pub scroll_direction: ScrollDirection,
pub table_state: TableState,
}
#[derive(Default)]
pub struct AppDeleteDialogState {
pub is_showing_dd: bool,
pub is_on_yes: bool, // Defaults to "No"
}
pub struct AppHelpDialogState {
pub is_showing_help: bool,
pub scroll_state: ParagraphScrollState,
pub index_shortcuts: Vec<u16>,
}
impl Default for AppHelpDialogState {
fn default() -> Self {
AppHelpDialogState {
is_showing_help: false,
scroll_state: ParagraphScrollState::default(),
index_shortcuts: vec![0; constants::HELP_TEXT.len()],
}
}
}
/// AppSearchState deals with generic searching (I might do this in the future).
pub struct AppSearchState {
pub is_enabled: bool,
pub current_search_query: String,
pub current_regex: Option<std::result::Result<regex::Regex, regex::Error>>,
pub is_blank_search: bool,
pub is_invalid_search: bool,
pub grapheme_cursor: GraphemeCursor,
pub cursor_direction: CursorDirection,
pub cursor_bar: usize,
/// This represents the position in terms of CHARACTERS, not graphemes
pub char_cursor_position: usize,
}
impl Default for AppSearchState {
fn default() -> Self {
AppSearchState {
is_enabled: false,
current_search_query: String::default(),
current_regex: None,
is_invalid_search: false,
is_blank_search: true,
grapheme_cursor: GraphemeCursor::new(0, 0, true),
cursor_direction: CursorDirection::RIGHT,
cursor_bar: 0,
char_cursor_position: 0,
}
}
}
impl AppSearchState {
/// Returns a reset but still enabled app search state
pub fn reset(&mut self) {
*self = AppSearchState {
is_enabled: self.is_enabled,
..AppSearchState::default()
}
}
pub fn is_invalid_or_blank_search(&self) -> bool {
self.is_blank_search || self.is_invalid_search
}
}
/// ProcessSearchState only deals with process' search's current settings and state.
pub struct ProcessSearchState {
pub search_state: AppSearchState,
pub is_searching_with_pid: bool,
pub is_ignoring_case: bool,
pub is_searching_whole_word: bool,
pub is_searching_with_regex: bool,
}
impl Default for ProcessSearchState {
fn default() -> Self {
ProcessSearchState {
search_state: AppSearchState::default(),
is_searching_with_pid: false,
is_ignoring_case: true,
is_searching_whole_word: false,
is_searching_with_regex: false,
}
}
}
impl ProcessSearchState {
pub fn search_toggle_ignore_case(&mut self) {
self.is_ignoring_case = !self.is_ignoring_case;
}
pub fn search_toggle_whole_word(&mut self) {
self.is_searching_whole_word = !self.is_searching_whole_word;
}
pub fn search_toggle_regex(&mut self) {
self.is_searching_with_regex = !self.is_searching_with_regex;
}
}
pub struct ProcWidgetState {
pub process_search_state: ProcessSearchState,
pub is_grouped: bool,
pub scroll_state: AppScrollWidgetState,
pub process_sorting_type: processes::ProcessSorting,
pub process_sorting_reverse: bool,
}
impl ProcWidgetState {
pub fn init(
is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool,
) -> Self {
let mut process_search_state = ProcessSearchState::default();
if is_case_sensitive {
// By default it's off
process_search_state.search_toggle_ignore_case();
}
if is_match_whole_word {
process_search_state.search_toggle_whole_word();
}
if is_use_regex {
process_search_state.search_toggle_regex();
}
ProcWidgetState {
process_search_state,
is_grouped,
scroll_state: AppScrollWidgetState::default(),
process_sorting_type: processes::ProcessSorting::CPU,
process_sorting_reverse: true,
}
}
pub fn get_cursor_position(&self) -> usize {
self.process_search_state
.search_state
.grapheme_cursor
.cur_cursor()
}
pub fn get_char_cursor_position(&self) -> usize {
self.process_search_state.search_state.char_cursor_position
}
pub fn is_search_enabled(&self) -> bool {
self.process_search_state.search_state.is_enabled
}
pub fn get_current_search_query(&self) -> &String {
&self.process_search_state.search_state.current_search_query
}
pub fn update_regex(&mut self) {
if self
.process_search_state
.search_state
.current_search_query
.is_empty()
{
self.process_search_state.search_state.is_invalid_search = false;
self.process_search_state.search_state.is_blank_search = true;
} else {
let regex_string = &self.process_search_state.search_state.current_search_query;
let escaped_regex: String;
let final_regex_string = &format!(
"{}{}{}{}",
if self.process_search_state.is_searching_whole_word {
"^"
} else {
""
},
if self.process_search_state.is_ignoring_case {
"(?i)"
} else {
""
},
if !self.process_search_state.is_searching_with_regex {
escaped_regex = regex::escape(regex_string);
&escaped_regex
} else {
regex_string
},
if self.process_search_state.is_searching_whole_word {
"$"
} else {
""
},
);
let new_regex = regex::Regex::new(final_regex_string);
self.process_search_state.search_state.is_blank_search = false;
self.process_search_state.search_state.is_invalid_search = new_regex.is_err();
self.process_search_state.search_state.current_regex = Some(new_regex);
}
self.scroll_state.previous_scroll_position = 0;
self.scroll_state.current_scroll_position = 0;
}
pub fn clear_search(&mut self) {
self.process_search_state.search_state.reset();
}
pub fn search_walk_forward(&mut self, start_position: usize) {
self.process_search_state
.search_state
.grapheme_cursor
.next_boundary(
&self.process_search_state.search_state.current_search_query[start_position..],
start_position,
)
.unwrap();
}
pub fn search_walk_back(&mut self, start_position: usize) {
self.process_search_state
.search_state
.grapheme_cursor
.prev_boundary(
&self.process_search_state.search_state.current_search_query[..start_position],
0,
)
.unwrap();
}
}
pub struct ProcState {
pub widget_states: HashMap<u64, ProcWidgetState>,
pub force_update: Option<u64>,
pub force_update_all: bool,
}
impl ProcState {
pub fn init(widget_states: HashMap<u64, ProcWidgetState>) -> Self {
ProcState {
widget_states,
force_update: None,
force_update_all: false,
}
}
}
pub struct NetWidgetState {
pub current_display_time: u64,
pub autohide_timer: Option<Instant>,
}
impl NetWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
NetWidgetState {
current_display_time,
autohide_timer,
}
}
}
pub struct NetState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, NetWidgetState>,
}
impl NetState {
pub fn init(widget_states: HashMap<u64, NetWidgetState>) -> Self {
NetState {
force_update: None,
widget_states,
}
}
}
pub struct CpuWidgetState {
pub current_display_time: u64,
pub is_legend_hidden: bool,
pub is_showing_tray: bool,
pub core_show_vec: Vec<bool>,
pub num_cpus_shown: usize,
pub autohide_timer: Option<Instant>,
pub scroll_state: AppScrollWidgetState,
}
impl CpuWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
CpuWidgetState {
current_display_time,
is_legend_hidden: false,
is_showing_tray: false,
core_show_vec: Vec::new(),
num_cpus_shown: 0,
autohide_timer,
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct CpuState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, CpuWidgetState>,
pub num_cpus_total: usize,
}
impl CpuState {
pub fn init(widget_states: HashMap<u64, CpuWidgetState>) -> Self {
CpuState {
force_update: None,
widget_states,
num_cpus_total: 0,
}
}
}
pub struct MemWidgetState {
pub current_display_time: u64,
pub autohide_timer: Option<Instant>,
}
impl MemWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
MemWidgetState {
current_display_time,
autohide_timer,
}
}
}
pub struct MemState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, MemWidgetState>,
}
impl MemState {
pub fn init(widget_states: HashMap<u64, MemWidgetState>) -> Self {
MemState {
force_update: None,
widget_states,
}
}
}
pub struct TempWidgetState {
pub scroll_state: AppScrollWidgetState,
}
impl TempWidgetState {
pub fn init() -> Self {
TempWidgetState {
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct TempState {
pub widget_states: HashMap<u64, TempWidgetState>,
}
impl TempState {
pub fn init(widget_states: HashMap<u64, TempWidgetState>) -> Self {
TempState { widget_states }
}
}
pub struct DiskWidgetState {
pub scroll_state: AppScrollWidgetState,
}
impl DiskWidgetState {
pub fn init() -> Self {
DiskWidgetState {
scroll_state: AppScrollWidgetState::default(),
}
}
}
pub struct DiskState {
pub widget_states: HashMap<u64, DiskWidgetState>,
}
impl DiskState {
pub fn init(widget_states: HashMap<u64, DiskWidgetState>) -> Self {
DiskState { widget_states }
}
}
pub struct BasicTableWidgetState {
// Since this is intended (currently) to only be used for ONE widget, that's
// how it's going to be written. If we want to allow for multiple of these,
// then we can expand outwards with a normal BasicTableState and a hashmap
pub currently_displayed_widget_type: BottomWidgetType,
pub currently_displayed_widget_id: u64,
pub widget_id: i64,
}
#[derive(Default)]
pub struct BatteryWidgetState {
pub currently_selected_battery_index: usize,
}
pub struct BatteryState {
pub widget_states: HashMap<u64, BatteryWidgetState>,
}
impl BatteryState {
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
BatteryState { widget_states }
}
}
#[derive(Default)]
pub struct ParagraphScrollState {
pub current_scroll_index: u16,
pub max_scroll_index: u16,
}

View file

@ -350,6 +350,7 @@ impl Painter {
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
_ => {}
@ -410,6 +411,13 @@ impl Painter {
false,
widget_id,
),
Battery => self.draw_battery_display(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
_ => {}
}
}
@ -541,9 +549,13 @@ impl Painter {
true,
widget.widget_id,
),
Battery => {
self.draw_battery_display(f, app_state, *widget_draw_loc, widget.widget_id)
}
Battery => self.draw_battery_display(
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
_ => {}
}
}

View file

@ -23,19 +23,34 @@ impl BasicTableArrows for Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
) {
// Effectively a paragraph with a ton of spacing
let (left_table, right_table) =
if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
match basic_table_widget_state.currently_displayed_widget_type {
BottomWidgetType::Proc | BottomWidgetType::ProcSearch => {
(BottomWidgetType::Temp, BottomWidgetType::Disk)
}
BottomWidgetType::Disk => (BottomWidgetType::Proc, BottomWidgetType::Temp),
BottomWidgetType::Temp => (BottomWidgetType::Disk, BottomWidgetType::Proc),
_ => (BottomWidgetType::Disk, BottomWidgetType::Temp),
}
} else {
(BottomWidgetType::Disk, BottomWidgetType::Temp)
};
let (left_table, right_table) = (
{
app_state
.current_widget
.left_neighbour
.map(|left_widget_id| {
app_state
.widget_map
.get(&left_widget_id)
.map(|left_widget| &left_widget.widget_type)
.unwrap_or_else(|| &BottomWidgetType::Temp)
})
.unwrap_or_else(|| &BottomWidgetType::Temp)
},
{
app_state
.current_widget
.right_neighbour
.map(|right_widget_id| {
app_state
.widget_map
.get(&right_widget_id)
.map(|right_widget| &right_widget.widget_type)
.unwrap_or_else(|| &BottomWidgetType::Disk)
})
.unwrap_or_else(|| &BottomWidgetType::Disk)
},
);
let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name();
@ -46,12 +61,9 @@ impl BasicTableArrows for Painter {
) as usize;
let arrow_text = vec![
Text::Styled(
format!("\n{}", right_name).into(),
self.colours.text_style,
),
Text::Styled(format!("\n{}", left_name).into(), self.colours.text_style),
Text::Raw(" ".repeat(num_spaces).into()),
Text::Styled(format!("{}", left_name).into(), self.colours.text_style),
Text::Styled(format!("{}", right_name).into(), self.colours.text_style),
];
let margined_draw_loc = Layout::default()

View file

@ -3,6 +3,7 @@ use std::cmp::max;
use crate::{
app::App,
canvas::{drawing_utils::calculate_basic_use_bars, Painter},
constants::*,
};
use tui::{
@ -14,17 +15,20 @@ use tui::{
pub trait BatteryDisplayWidget {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
impl BatteryDisplayWidget for Painter {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
if let Some(battery_widget_state) =
app_state.battery_state.widget_states.get_mut(&widget_id)
{
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Battery ── Esc to go back ";
let repeat_num = max(
@ -40,21 +44,29 @@ impl BatteryDisplayWidget for Painter {
" Battery ".to_string()
};
let border_and_title_style = if app_state.current_widget.widget_id == widget_id {
let border_and_title_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let battery_block = Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style);
let battery_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
if let Some(battery_details) = app_state
.canvas_data

View file

@ -94,7 +94,11 @@ impl TempTableWidget for Painter {
let temp_block = if draw_border {
Block::default()
.title(&title)
.title_style(border_and_title_style)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style)
} else if is_on_widget {

View file

@ -92,6 +92,7 @@ fn get_matches() -> clap::ArgMatches<'static> {
(@arg DEFAULT_WIDGET_COUNT: --default_widget_count +takes_value "Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1.")
(@arg USE_OLD_NETWORK_LEGEND: --use_old_network_legend "Use the older (pre-0.4) network widget legend.")
(@arg HIDE_TABLE_GAP: --hide_table_gap "Hides the spacing between the table headers and entries.")
(@arg BATTERY: --battery "Shows the battery widget in default or basic mode. No effect on custom layouts.")
)
.get_matches()
}

View file

@ -42,6 +42,7 @@ pub struct ConfigFlags {
pub default_widget_count: Option<u64>,
pub use_old_network_legend: Option<bool>,
pub hide_table_gap: Option<bool>,
pub battery: Option<bool>,
}
#[derive(Default, Deserialize)]
@ -261,7 +262,7 @@ pub fn get_widget_layout(
let bottom_layout = if get_use_basic_mode(matches, config) {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_basic_default()
BottomLayout::init_basic_default(get_use_battery(matches, config))
} else if let Some(rows) = &config.row {
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0;
@ -283,7 +284,7 @@ pub fn get_widget_layout(
total_row_height_ratio: total_height_ratio,
};
// Confirm that we have at least ONE widget - if we don't, go back to default!
// Confirm that we have at least ONE widget - if not, error out!
if iter_id > 0 {
ret_bottom_layout.get_movement_mappings();
ret_bottom_layout
@ -294,7 +295,7 @@ pub fn get_widget_layout(
}
} else {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_default(left_legend)
BottomLayout::init_default(left_legend, get_use_battery(matches, config))
};
Ok((bottom_layout, default_widget_id))
@ -636,3 +637,16 @@ pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config)
}
false
}
pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("BATTERY") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(battery) = flags.battery {
if battery {
return true;
}
}
}
false
}