summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--Cargo.toml4
-rw-r--r--src/bin/main.rs185
-rw-r--r--src/lib.rs (renamed from src/main.rs)216
-rw-r--r--src/options.rs9
-rw-r--r--tests/layout_management_tests.rs469
-rw-r--r--tests/widget_movement_tests.rs1
7 files changed, 689 insertions, 199 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 124edecd..ed31f95e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,9 +38,11 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re
- I develop primarily using _stable_ Rust. That is, whatever is the most up-to-date stable version you can get via running
`rustup update stable`.
+- There are some tests, they're mostly for sanity checks. Please run `cargo test` to ensure you didn't break anything important, unless the change will break the test (in which case please amend the tests).
+
- Note that `cargo test` will fail on anything lower than 1.43.0 due to it using a then-introduced env variable.
-- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to pass CI.
+- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to for PRs to be accepted.
- You can check clippy using `cargo +nightly clippy`.
diff --git a/Cargo.toml b/Cargo.toml
index 1da73fdb..7a908c72 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,10 +9,12 @@ license = "MIT"
categories = ["command-line-utilities", "visualization"]
description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows."
readme = "README.md"
+default-run = "btm"
[[bin]]
name = "btm"
-path = "src/main.rs"
+path = "src/bin/main.rs"
+doc = false
[profile.release]
debug = 1
diff --git a/src/bin/main.rs b/src/bin/main.rs
new file mode 100644
index 00000000..fdbe7b51
--- /dev/null
+++ b/src/bin/main.rs
@@ -0,0 +1,185 @@
+#![warn(rust_2018_idioms)]
+#[allow(unused_imports)]
+#[macro_use]
+extern crate log;
+
+use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *};
+
+use std::{
+ boxed::Box,
+ io::{stdout, Write},
+ panic,
+ sync::mpsc,
+ thread,
+ time::Duration,
+};
+
+use crossterm::{
+ event::EnableMouseCapture,
+ execute,
+ terminal::{enable_raw_mode, EnterAlternateScreen},
+};
+use tui::{backend::CrosstermBackend, Terminal};
+
+fn main() -> error::Result<()> {
+ #[cfg(debug_assertions)]
+ {
+ utils::logging::init_logger()?;
+ }
+ let matches = get_matches();
+
+ let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
+
+ // Get widget layout separately
+ let (widget_layout, default_widget_id, default_widget_type_option) =
+ get_widget_layout(&matches, &config)?;
+
+ // Create "app" struct, which will control most of the program and store settings/state
+ let mut app = build_app(
+ &matches,
+ &config,
+ &widget_layout,
+ default_widget_id,
+ &default_widget_type_option,
+ )?;
+
+ // Create painter and set colours.
+ let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
+ generate_config_colours(&config, &mut painter)?;
+ painter.colours.generate_remaining_cpu_colours();
+ painter.complete_painter_init();
+
+ // Set up input handling
+ let (sender, receiver) = mpsc::channel();
+ create_input_thread(sender.clone());
+
+ // Cleaning loop
+ {
+ let cleaning_sender = sender.clone();
+ thread::spawn(move || loop {
+ thread::sleep(Duration::from_millis(
+ constants::STALE_MAX_MILLISECONDS + 5000,
+ ));
+ if cleaning_sender.send(BottomEvent::Clean).is_err() {
+ break;
+ }
+ });
+ }
+
+ // Event loop
+ let (reset_sender, reset_receiver) = mpsc::channel();
+ create_event_thread(
+ sender,
+ reset_receiver,
+ app.app_config_fields.use_current_cpu_total,
+ app.app_config_fields.update_rate_in_milliseconds,
+ app.app_config_fields.temperature_type.clone(),
+ app.app_config_fields.show_average_cpu,
+ app.used_widgets.clone(),
+ );
+
+ // Set up up tui and crossterm
+ let mut stdout_val = stdout();
+ execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
+ enable_raw_mode()?;
+
+ let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
+ terminal.hide_cursor()?;
+
+ // Set panic hook
+ panic::set_hook(Box::new(|info| panic_hook(info)));
+
+ loop {
+ if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
+ match recv {
+ BottomEvent::KeyInput(event) => {
+ if handle_key_event_or_break(event, &mut app, &reset_sender) {
+ break;
+ }
+ handle_force_redraws(&mut app);
+ }
+ BottomEvent::MouseInput(event) => {
+ handle_mouse_event(event, &mut app);
+ handle_force_redraws(&mut app);
+ }
+ BottomEvent::Update(data) => {
+ app.data_collection.eat_data(&data);
+
+ if !app.is_frozen {
+ // Convert all data into tui-compliant components
+
+ // Network
+ if app.used_widgets.use_net {
+ let network_data = convert_network_data_points(
+ &app.data_collection,
+ false,
+ app.app_config_fields.use_basic_mode
+ || app.app_config_fields.use_old_network_legend,
+ );
+ app.canvas_data.network_data_rx = network_data.rx;
+ app.canvas_data.network_data_tx = network_data.tx;
+ app.canvas_data.rx_display = network_data.rx_display;
+ app.canvas_data.tx_display = network_data.tx_display;
+ if let Some(total_rx_display) = network_data.total_rx_display {
+ app.canvas_data.total_rx_display = total_rx_display;
+ }
+ if let Some(total_tx_display) = network_data.total_tx_display {
+ app.canvas_data.total_tx_display = total_tx_display;
+ }
+ }
+
+ // Disk
+ if app.used_widgets.use_disk {
+ app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
+ }
+
+ // Temperatures
+ if app.used_widgets.use_temp {
+ app.canvas_data.temp_sensor_data = convert_temp_row(&app);
+ }
+
+ // Memory
+ if app.used_widgets.use_mem {
+ app.canvas_data.mem_data =
+ convert_mem_data_points(&app.data_collection, false);
+ app.canvas_data.swap_data =
+ convert_swap_data_points(&app.data_collection, false);
+ let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
+ app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
+ app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
+ app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
+ app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
+ }
+
+ if app.used_widgets.use_cpu {
+ // CPU
+ app.canvas_data.cpu_data =
+ convert_cpu_data_points(&app.data_collection, false);
+ }
+
+ // Processes
+ if app.used_widgets.use_proc {
+ update_all_process_lists(&mut app);
+ }
+
+ // Battery
+ if app.used_widgets.use_battery {
+ app.canvas_data.battery_data =
+ convert_battery_harvest(&app.data_collection);
+ }
+ }
+ }
+ BottomEvent::Clean => {
+ app.data_collection
+ .clean_data(constants::STALE_MAX_MILLISECONDS);
+ }
+ }
+ }
+
+ // TODO: [OPT] Should not draw if no change (ie: scroll max)
+ try_drawing(&mut terminal, &mut app, &mut painter)?;
+ }
+
+ cleanup_terminal(&mut terminal)?;
+ Ok(())
+}
diff --git a/src/main.rs b/src/lib.rs
index 50ea11bc..53d7430b 100644
--- a/src/main.rs
+++ b/src/lib.rs
@@ -1,14 +1,7 @@
-#![warn(rust_2018_idioms)]
-
-#[allow(unused_imports)]
-#[macro_use]
-extern crate log;
-
use std::{
boxed::Box,
io::{stdout, Write},
- panic::{self, PanicInfo},
- sync::mpsc,
+ panic::PanicInfo,
thread,
time::{Duration, Instant},
};
@@ -16,15 +9,11 @@ use std::{
use clap::*;
use crossterm::{
- event::{
- poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent,
- KeyModifiers, MouseEvent,
- },
+ event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
execute,
style::Print,
- terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
+ terminal::{disable_raw_mode, LeaveAlternateScreen},
};
-use tui::{backend::CrosstermBackend, Terminal};
use app::{
data_harvester::{self, processes::ProcessSorting},
@@ -38,30 +27,29 @@ use utils::error;
pub mod app;
-mod utils {
+pub mod utils {
pub mod error;
pub mod gen_util;
pub mod logging;
}
-mod canvas;
-mod constants;
-mod data_conversion;
-
+pub mod canvas;
+pub mod constants;
+pub mod data_conversion;
pub mod options;
-enum BottomEvent<I, J> {
+pub enum BottomEvent<I, J> {
KeyInput(I),
MouseInput(J),
Update(Box<data_harvester::Data>),
Clean,
}
-enum ResetEvent {
+pub enum ResetEvent {
Reset,
}
-fn get_matches() -> clap::ArgMatches<'static> {
+pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app!(app =>
(name: crate_name!())
(version: crate_version!())
@@ -96,163 +84,7 @@ fn get_matches() -> clap::ArgMatches<'static> {
.get_matches()
}
-fn main() -> error::Result<()> {
- #[cfg(debug_assertions)]
- {
- utils::logging::init_logger()?;
- }
- let matches = get_matches();
-
- let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
-
- // Get widget layout separately
- let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?;
-
- // Create "app" struct, which will control most of the program and store settings/state
- let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?;
-
- // Create painter and set colours.
- let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
- generate_config_colours(&config, &mut painter)?;
- painter.colours.generate_remaining_cpu_colours();
- painter.complete_painter_init();
-
- // Set up input handling
- let (sender, receiver) = mpsc::channel();
- create_input_thread(sender.clone());
-
- // Cleaning loop
- {
- let cleaning_sender = sender.clone();
- thread::spawn(move || loop {
- thread::sleep(Duration::from_millis(
- constants::STALE_MAX_MILLISECONDS + 5000,
- ));
- if cleaning_sender.send(BottomEvent::Clean).is_err() {
- break;
- }
- });
- }
-
- // Event loop
- let (reset_sender, reset_receiver) = mpsc::channel();
- create_event_thread(
- sender,
- reset_receiver,
- app.app_config_fields.use_current_cpu_total,
- app.app_config_fields.update_rate_in_milliseconds,
- app.app_config_fields.temperature_type.clone(),
- app.app_config_fields.show_average_cpu,
- app.used_widgets.clone(),
- );
-
- // Set up up tui and crossterm
- let mut stdout_val = stdout();
- execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
- enable_raw_mode()?;
-
- let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
- terminal.hide_cursor()?;
-
- // Set panic hook
- panic::set_hook(Box::new(|info| panic_hook(info)));
-
- loop {
- if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
- match recv {
- BottomEvent::KeyInput(event) => {
- if handle_key_event_or_break(event, &mut app, &reset_sender) {
- break;
- }
- handle_force_redraws(&mut app);
- }
- BottomEvent::MouseInput(event) => {
- handle_mouse_event(event, &mut app);
- handle_force_redraws(&mut app);
- }
- BottomEvent::Update(data) => {
- app.data_collection.eat_data(&data);
-
- if !app.is_frozen {
- // Convert all data into tui-compliant components
-
- // Network
- if app.used_widgets.use_net {
- let network_data = convert_network_data_points(
- &app.data_collection,
- false,
- app.app_config_fields.use_basic_mode
- || app.app_config_fields.use_old_network_legend,
- );
- app.canvas_data.network_data_rx = network_data.rx;
- app.canvas_data.network_data_tx = network_data.tx;
- app.canvas_data.rx_display = network_data.rx_display;
- app.canvas_data.tx_display = network_data.tx_display;
- if let Some(total_rx_display) = network_data.total_rx_display {
- app.canvas_data.total_rx_display = total_rx_display;
- }
- if let Some(total_tx_display) = network_data.total_tx_display {
- app.canvas_data.total_tx_display = total_tx_display;
- }
- }
-
- // Disk
- if app.used_widgets.use_disk {
- app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
- }
-
- // Temperatures
- if app.used_widgets.use_temp {
- app.canvas_data.temp_sensor_data = convert_temp_row(&app);
- }
-
- // Memory
- if app.used_widgets.use_mem {
- app.canvas_data.mem_data =
- convert_mem_data_points(&app.data_collection, false);
- app.canvas_data.swap_data =
- convert_swap_data_points(&app.data_collection, false);
- let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
- app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
- app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
- app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
- app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
- }
-
- if app.used_widgets.use_cpu {
- // CPU
- app.canvas_data.cpu_data =
- convert_cpu_data_points(&app.data_collection, false);
- }
-
- // Processes
- if app.used_widgets.use_proc {
- update_all_process_lists(&mut app);
- }
-
- // Battery
- if app.used_widgets.use_battery {
- app.canvas_data.battery_data =
- convert_battery_harvest(&app.data_collection);
- }
- }
- }
- BottomEvent::Clean => {
- app.data_collection
- .clean_data(constants::STALE_MAX_MILLISECONDS);
- }
- }
- }
-
- // TODO: [OPT] Should not draw if no change (ie: scroll max)
- try_drawing(&mut terminal, &mut app, &mut painter)?;
- }
-
- cleanup_terminal(&mut terminal)?;
- Ok(())
-}
-
-fn handle_mouse_event(event: MouseEvent, app: &mut App) {
+pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
match event {
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),
MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(),
@@ -260,7 +92,7 @@ fn handle_mouse_event(event: MouseEvent, app: &mut App) {
};
}
-fn handle_key_event_or_break(
+pub fn handle_key_event_or_break(
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>,
) -> bool {
// debug!("KeyEvent: {:?}", event);
@@ -348,7 +180,7 @@ fn handle_key_event_or_break(
false
}
-fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
+pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
use std::{ffi::OsString, fs};
let config_path = if let Some(conf_loc) = flag_config_location {
Some(OsString::from(conf_loc))
@@ -399,7 +231,7 @@ fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
}
}
-fn try_drawing(
+pub fn try_drawing(
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
app: &mut App, painter: &mut canvas::Painter,
) -> error::Result<()> {
@@ -411,7 +243,7 @@ fn try_drawing(
Ok(())
}
-fn cleanup_terminal(
+pub fn cleanup_terminal(
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
) -> error::Result<()> {
disable_raw_mode()?;
@@ -425,7 +257,9 @@ fn cleanup_terminal(
Ok(())
}
-fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> error::Result<()> {
+pub fn generate_config_colours(
+ config: &Config, painter: &mut canvas::Painter,
+) -> error::Result<()> {
if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color {
painter.colours.set_border_colour(border_color)?;
@@ -514,7 +348,7 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er
}
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
-fn panic_hook(panic_info: &PanicInfo<'_>) {
+pub fn panic_hook(panic_info: &PanicInfo<'_>) {
let mut stdout = stdout();
let msg = match panic_info.payload().downcast_ref::<&'static str>() {
@@ -543,7 +377,7 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
.unwrap();
}
-fn handle_force_redraws(app: &mut App) {
+pub fn handle_force_redraws(app: &mut App) {
// Currently we use an Option... because we might want to future-proof this
// if we eventually get widget-specific redrawing!
if app.proc_state.force_update_all {
@@ -573,7 +407,7 @@ fn handle_force_redraws(app: &mut App) {
}
}
-fn update_all_process_lists(app: &mut App) {
+pub fn update_all_process_lists(app: &mut App) {
let widget_ids = app
.proc_state
.widget_states
@@ -586,7 +420,7 @@ fn update_all_process_lists(app: &mut App) {
});
}
-fn update_final_process_list(app: &mut App, widget_id: u64) {
+pub fn update_final_process_list(app: &mut App, widget_id: u64) {
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
Some(process_state) => process_state
.process_search_state
@@ -659,7 +493,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
}
}
-fn sort_process_data(
+pub fn sort_process_data(
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
) {
to_sort_vec.sort_by(|a, b| {
@@ -763,7 +597,7 @@ fn sort_process_data(
}
}
-fn create_input_thread(
+pub fn create_input_thread(
sender: std::sync::mpsc::Sender<
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
>,
@@ -796,7 +630,7 @@ fn create_input_thread(
});
}
-fn create_event_thread(
+pub fn create_event_thread(
sender: std::sync::mpsc::Sender<
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
>,
diff --git a/src/options.rs b/src/options.rs
index e9fb4a29..d772ed9e 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -10,7 +10,7 @@ use crate::{
use layout_options::*;
-mod layout_options;
+pub mod layout_options;
#[derive(Default, Deserialize)]
pub struct Config {
@@ -68,7 +68,7 @@ pub struct ConfigColours {
pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
- default_widget_id: u64,
+ default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
) -> error::Result<App> {
use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config);
@@ -96,7 +96,6 @@ pub fn build_app(
None
};
- let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
let mut initial_widget_id: u64 = default_widget_id;
let mut initial_widget_type = Proc;
let is_custom_layout = config.row.is_some();
@@ -252,7 +251,7 @@ pub fn build_app(
pub fn get_widget_layout(
matches: &clap::ArgMatches<'static>, config: &Config,
-) -> error::Result<(BottomLayout, u64)> {
+) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
let left_legend = get_use_left_legend(matches, config);
let (default_widget_type, mut default_widget_count) =
get_default_widget_and_count(matches, config)?;
@@ -311,7 +310,7 @@ pub fn get_widget_layout(
}
};
- Ok((bottom_layout, default_widget_id))
+ Ok((bottom_layout, default_widget_id, default_widget_type))
}
fn get_update_rate_in_milliseconds(
diff --git a/tests/layout_management_tests.rs b/tests/layout_management_tests.rs
new file mode 100644
index 00000000..e4eb21f4
--- /dev/null
+++ b/tests/layout_management_tests.rs
@@ -0,0 +1,469 @@
+//! Mocks layout management, so we can check if we broke anything.
+
+use bottom::app::layout_manager::{BottomLayout, BottomWidgetType};
+use bottom::constants::{DEFAULT_BATTERY_LAYOUT, DEFAULT_LAYOUT, DEFAULT_WIDGET_ID};
+use bottom::options::{layout_options::Row, Config};
+use bottom::utils::error;
+
+const PROC_LAYOUT: &str = r##"
+[[row]]
+ [[row.child]]
+ type="proc"
+[[row]]
+ [[row.child]]
+ type="proc"
+ [[row.child]]
+ type="proc"
+[[row]]
+ [[row.child]]
+ type="proc"
+ [[row.child]]
+ type="proc"
+"##;
+
+fn test_create_layout(
+ rows: &Vec<Row>, default_widget_id: u64, default_widget_type: Option<BottomWidgetType>,
+ default_widget_count: u64, left_legend: bool,
+) -> BottomLayout {
+ let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
+ let mut total_height_ratio = 0;
+ let mut default_widget_count = default_widget_count;
+ let mut default_widget_id = default_widget_id;
+
+ let mut ret_bottom_layout = BottomLayout {
+ rows: rows
+ .iter()
+ .map(|row| {
+ row.convert_row_to_bottom_row(
+ &mut iter_id,
+ &mut total_height_ratio,
+ &mut default_widget_id,
+ &default_widget_type,
+ &mut default_widget_count,
+ left_legend,
+ )
+ })
+ .collect::<error::Result<Vec<_>>>()
+ .unwrap(),
+ total_row_height_ratio: total_height_ratio,
+ };
+ ret_bottom_layout.get_movement_mappings();
+
+ ret_bottom_layout
+}
+
+#[test]
+/// Tests the default setup.
+fn test_default_movement() {
+ let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
+ .unwrap()
+ .row
+ .unwrap();
+ let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
+
+ // Simple tests for the top CPU widget
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
+ Some(3)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
+ Some(2)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
+ None
+ );
+
+ // Test CPU legend
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
+ Some(4)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
+ Some(1)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
+ None
+ );
+
+ // Test memory->temp, temp->disk, disk->memory mappings
+ assert_eq!(
+ ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour,
+ Some(4)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[1].children[1].children[0].children[0].down_neighbour,
+ Some(5)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[1].children[1].children[1].children[0].left_neighbour,
+ Some(3)
+ );
+
+ // Test disk -> processes, processes -> process sort, process sort -> network
+ assert_eq!(
+ ret_bottom_layout.rows[1].children[1].children[1].children[0].down_neighbour,
+ Some(7)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
+ Some(9)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
+ Some(6)
+ );
+}
+
+#[test]
+/// Tests battery movement in the default setup.
+fn test_default_battery_movement() {
+ let rows = toml::from_str::<Config>(DEFAULT_BATTERY_LAYOUT)
+ .unwrap()
+ .row
+ .unwrap();
+ let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
+
+ // Simple tests for the top CPU widget
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
+ Some(4)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
+ Some(2)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
+ None
+ );
+
+ // Test CPU legend
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
+ Some(5)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
+ Some(3)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
+ Some(1)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
+ None
+ );
+}
+
+#[test]
+/// Tests using left_legend.
+fn test_left_legend() {
+ let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
+ .unwrap()
+ .row
+ .unwrap();
+ let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true);
+
+ // Legend
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
+ Some(3)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
+ Some(1)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
+ None
+ );
+
+ // Widget
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
+ Some(3)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
+ Some(2)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
+ None
+ );
+}
+
+#[test]
+/// Tests explicit default widget.
+fn test_default_widget_in_layout() {
+ let proc_layout = r##"
+ [[row]]
+ [[row.child]]
+ type="proc"
+ [[row]]
+ [[row.child]]
+ type="proc"
+ [[row.child]]
+ type="proc"
+ [[row]]
+ [[row.child]]
+ type="proc"
+ default=true
+ [[row.child]]
+ type="proc"
+ "##;
+ let rows = toml::from_str::<Config>(proc_layout).unwrap().row.unwrap();
+ let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
+ let mut total_height_ratio = 0;
+ let mut default_widget_count = 1;
+ let mut default_widget_id = DEFAULT_WIDGET_ID;
+ let default_widget_type = None;
+ let left_legend = false;
+
+ let mut ret_bottom_layout = BottomLayout {
+ rows: rows
+ .iter()
+ .map(|row| {
+ row.convert_row_to_bottom_row(
+ &mut iter_id,
+ &mut total_height_ratio,
+ &mut default_widget_id,
+ &default_widget_type,
+ &mut default_widget_count,
+ left_legend,
+ )
+ })
+ .collect::<error::Result<Vec<_>>>()
+ .unwrap(),
+ total_row_height_ratio: total_height_ratio,
+ };
+ ret_bottom_layout.get_movement_mappings();
+
+ assert_eq!(default_widget_id, 10);
+}
+
+#[test]
+/// Tests default widget by setting type and count.
+fn test_default_widget_by_option() {
+ let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
+ let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
+ let mut total_height_ratio = 0;
+ let mut default_widget_count = 3;
+ let mut default_widget_id = DEFAULT_WIDGET_ID;
+ let default_widget_type = Some(BottomWidgetType::Proc);
+ let left_legend = false;
+
+ let mut ret_bottom_layout = BottomLayout {
+ rows: rows
+ .iter()
+ .map(|row| {
+ row.convert_row_to_bottom_row(
+ &mut iter_id,
+ &mut total_height_ratio,
+ &mut default_widget_id,
+ &default_widget_type,
+ &mut default_widget_count,
+ left_legend,
+ )
+ })
+ .collect::<error::Result<Vec<_>>>()
+ .unwrap(),
+ total_row_height_ratio: total_height_ratio,
+ };
+ ret_bottom_layout.get_movement_mappings();
+
+ assert_eq!(default_widget_id, 7);
+}
+
+#[test]
+fn test_proc_custom_layout() {
+ let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
+ let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
+
+ // First proc widget
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
+ Some(2)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
+ Some(3)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
+ None
+ );
+
+ // Its search
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[1].children[0].down_neighbour,
+ Some(4)
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[1].children[0].left_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom_layout.rows[0].children[0].children[1].children[0].right_neighbour,
+ None
+ );
+ assert_eq!(
+ ret_bottom