mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00:00
refactor: Refactor code, add new tests
Refactor code so we use a lib, allowing for easier testing. Adds additional tests for layouts.
This commit is contained in:
parent
f6aa8e5d1d
commit
4b03b4b0b0
7 changed files with 689 additions and 199 deletions
|
@ -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
|
- 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`.
|
`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.
|
- 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`.
|
- You can check clippy using `cargo +nightly clippy`.
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,12 @@ license = "MIT"
|
||||||
categories = ["command-line-utilities", "visualization"]
|
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."
|
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"
|
readme = "README.md"
|
||||||
|
default-run = "btm"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "btm"
|
name = "btm"
|
||||||
path = "src/main.rs"
|
path = "src/bin/main.rs"
|
||||||
|
doc = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
185
src/bin/main.rs
Normal file
185
src/bin/main.rs
Normal file
|
@ -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(())
|
||||||
|
}
|
|
@ -1,14 +1,7 @@
|
||||||
#![warn(rust_2018_idioms)]
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
panic::{self, PanicInfo},
|
panic::PanicInfo,
|
||||||
sync::mpsc,
|
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
@ -16,15 +9,11 @@ use std::{
|
||||||
use clap::*;
|
use clap::*;
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{
|
event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
|
||||||
poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent,
|
|
||||||
KeyModifiers, MouseEvent,
|
|
||||||
},
|
|
||||||
execute,
|
execute,
|
||||||
style::Print,
|
style::Print,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use tui::{backend::CrosstermBackend, Terminal};
|
|
||||||
|
|
||||||
use app::{
|
use app::{
|
||||||
data_harvester::{self, processes::ProcessSorting},
|
data_harvester::{self, processes::ProcessSorting},
|
||||||
|
@ -38,30 +27,29 @@ use utils::error;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
||||||
mod utils {
|
pub mod utils {
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod gen_util;
|
pub mod gen_util;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod canvas;
|
pub mod canvas;
|
||||||
mod constants;
|
pub mod constants;
|
||||||
mod data_conversion;
|
pub mod data_conversion;
|
||||||
|
|
||||||
pub mod options;
|
pub mod options;
|
||||||
|
|
||||||
enum BottomEvent<I, J> {
|
pub enum BottomEvent<I, J> {
|
||||||
KeyInput(I),
|
KeyInput(I),
|
||||||
MouseInput(J),
|
MouseInput(J),
|
||||||
Update(Box<data_harvester::Data>),
|
Update(Box<data_harvester::Data>),
|
||||||
Clean,
|
Clean,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResetEvent {
|
pub enum ResetEvent {
|
||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_matches() -> clap::ArgMatches<'static> {
|
pub fn get_matches() -> clap::ArgMatches<'static> {
|
||||||
clap_app!(app =>
|
clap_app!(app =>
|
||||||
(name: crate_name!())
|
(name: crate_name!())
|
||||||
(version: crate_version!())
|
(version: crate_version!())
|
||||||
|
@ -96,163 +84,7 @@ fn get_matches() -> clap::ArgMatches<'static> {
|
||||||
.get_matches()
|
.get_matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> error::Result<()> {
|
pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
|
||||||
#[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) {
|
|
||||||
match event {
|
match event {
|
||||||
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),
|
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),
|
||||||
MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(),
|
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>,
|
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// debug!("KeyEvent: {:?}", event);
|
// debug!("KeyEvent: {:?}", event);
|
||||||
|
@ -348,7 +180,7 @@ fn handle_key_event_or_break(
|
||||||
false
|
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};
|
use std::{ffi::OsString, fs};
|
||||||
let config_path = if let Some(conf_loc) = flag_config_location {
|
let config_path = if let Some(conf_loc) = flag_config_location {
|
||||||
Some(OsString::from(conf_loc))
|
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>>,
|
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
||||||
app: &mut App, painter: &mut canvas::Painter,
|
app: &mut App, painter: &mut canvas::Painter,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
|
@ -411,7 +243,7 @@ fn try_drawing(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup_terminal(
|
pub fn cleanup_terminal(
|
||||||
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
|
@ -425,7 +257,9 @@ fn cleanup_terminal(
|
||||||
Ok(())
|
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(colours) = &config.colors {
|
||||||
if let Some(border_color) = &colours.border_color {
|
if let Some(border_color) = &colours.border_color {
|
||||||
painter.colours.set_border_colour(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
|
/// 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 mut stdout = stdout();
|
||||||
|
|
||||||
let msg = match panic_info.payload().downcast_ref::<&'static str>() {
|
let msg = match panic_info.payload().downcast_ref::<&'static str>() {
|
||||||
|
@ -543,7 +377,7 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
|
||||||
.unwrap();
|
.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
|
// Currently we use an Option... because we might want to future-proof this
|
||||||
// if we eventually get widget-specific redrawing!
|
// if we eventually get widget-specific redrawing!
|
||||||
if app.proc_state.force_update_all {
|
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
|
let widget_ids = app
|
||||||
.proc_state
|
.proc_state
|
||||||
.widget_states
|
.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) {
|
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
|
||||||
Some(process_state) => process_state
|
Some(process_state) => process_state
|
||||||
.process_search_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: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
|
||||||
) {
|
) {
|
||||||
to_sort_vec.sort_by(|a, b| {
|
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<
|
sender: std::sync::mpsc::Sender<
|
||||||
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
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<
|
sender: std::sync::mpsc::Sender<
|
||||||
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||||
>,
|
>,
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
|
|
||||||
use layout_options::*;
|
use layout_options::*;
|
||||||
|
|
||||||
mod layout_options;
|
pub mod layout_options;
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -68,7 +68,7 @@ pub struct ConfigColours {
|
||||||
|
|
||||||
pub fn build_app(
|
pub fn build_app(
|
||||||
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
|
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> {
|
) -> error::Result<App> {
|
||||||
use BottomWidgetType::*;
|
use BottomWidgetType::*;
|
||||||
let autohide_time = get_autohide_time(&matches, &config);
|
let autohide_time = get_autohide_time(&matches, &config);
|
||||||
|
@ -96,7 +96,6 @@ pub fn build_app(
|
||||||
None
|
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_id: u64 = default_widget_id;
|
||||||
let mut initial_widget_type = Proc;
|
let mut initial_widget_type = Proc;
|
||||||
let is_custom_layout = config.row.is_some();
|
let is_custom_layout = config.row.is_some();
|
||||||
|
@ -252,7 +251,7 @@ pub fn build_app(
|
||||||
|
|
||||||
pub fn get_widget_layout(
|
pub fn get_widget_layout(
|
||||||
matches: &clap::ArgMatches<'static>, config: &Config,
|
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 left_legend = get_use_left_legend(matches, config);
|
||||||
let (default_widget_type, mut default_widget_count) =
|
let (default_widget_type, mut default_widget_count) =
|
||||||
get_default_widget_and_count(matches, config)?;
|
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(
|
fn get_update_rate_in_milliseconds(
|
||||||
|
|
469
tests/layout_management_tests.rs
Normal file
469
tests/layout_management_tests.rs
Normal file
|
@ -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_layout.rows[0].children[0].children[1].children[0].up_neighbour,
|
||||||
|
Some(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Its sort
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_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].right_neighbour,
|
||||||
|
Some(1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
|
||||||
|
// Let us now test the second row's first widget...
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[1].down_neighbour,
|
||||||
|
Some(5)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[1].left_neighbour,
|
||||||
|
Some(6)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[1].right_neighbour,
|
||||||
|
Some(9)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[1].up_neighbour,
|
||||||
|
Some(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[0].down_neighbour,
|
||||||
|
Some(5)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[0].children[0].left_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
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[0].children[0].children[0].up_neighbour,
|
||||||
|
Some(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[1].children[0].down_neighbour,
|
||||||
|
Some(10)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[1].children[0].left_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[1].children[0].right_neighbour,
|
||||||
|
Some(8)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[1].children[0].children[1].children[0].up_neighbour,
|
||||||
|
Some(4)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Third row, second
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[1].down_neighbour,
|
||||||
|
Some(14)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
|
||||||
|
Some(15)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[1].right_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[1].up_neighbour,
|
||||||
|
Some(8)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[0].down_neighbour,
|
||||||
|
Some(14)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
|
||||||
|
Some(10)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[0].right_neighbour,
|
||||||
|
Some(13)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[0].children[0].up_neighbour,
|
||||||
|
Some(8)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[1].children[0].down_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[1].children[0].left_neighbour,
|
||||||
|
Some(11)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[1].children[0].right_neighbour,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ret_bottom_layout.rows[2].children[1].children[1].children[0].up_neighbour,
|
||||||
|
Some(13)
|
||||||
|
);
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
// TODO: See if we can mock widget movements and test if they work
|
|
Loading…
Reference in a new issue