refactor: Add sort capabilities to processes

This commit is contained in:
ClementTsang 2021-08-30 01:30:21 -04:00
parent 3fa50605b3
commit 27736b7fc0
9 changed files with 335 additions and 125 deletions

View File

@ -17,3 +17,6 @@ pub use text_input::TextInput;
pub mod carousel; pub mod carousel;
pub use carousel::Carousel; pub use carousel::Carousel;
pub mod sort_menu;
pub use sort_menu::SortMenu;

View File

@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEve
use tui::{layout::Rect, widgets::TableState}; use tui::{layout::Rect, widgets::TableState};
use crate::app::{ use crate::app::{
event::{WidgetEventResult, MultiKey, MultiKeyResult}, event::{MultiKey, MultiKeyResult, WidgetEventResult},
Component, Component,
}; };
@ -110,7 +110,7 @@ impl Scrollable {
} }
/// Update the index with this! This will automatically update the scroll direction as well! /// Update the index with this! This will automatically update the scroll direction as well!
fn update_index(&mut self, new_index: usize) { pub fn set_index(&mut self, new_index: usize) {
use std::cmp::Ordering; use std::cmp::Ordering;
match new_index.cmp(&self.current_index) { match new_index.cmp(&self.current_index) {
@ -130,7 +130,7 @@ impl Scrollable {
fn skip_to_first(&mut self) -> WidgetEventResult { fn skip_to_first(&mut self) -> WidgetEventResult {
if self.current_index != 0 { if self.current_index != 0 {
self.update_index(0); self.set_index(0);
WidgetEventResult::Redraw WidgetEventResult::Redraw
} else { } else {
@ -141,7 +141,7 @@ impl Scrollable {
fn skip_to_last(&mut self) -> WidgetEventResult { fn skip_to_last(&mut self) -> WidgetEventResult {
let last_index = self.num_items - 1; let last_index = self.num_items - 1;
if self.current_index != last_index { if self.current_index != last_index {
self.update_index(last_index); self.set_index(last_index);
WidgetEventResult::Redraw WidgetEventResult::Redraw
} else { } else {
@ -161,7 +161,7 @@ impl Scrollable {
} else if self.current_index == new_index { } else if self.current_index == new_index {
WidgetEventResult::NoRedraw WidgetEventResult::NoRedraw
} else { } else {
self.update_index(new_index); self.set_index(new_index);
WidgetEventResult::Redraw WidgetEventResult::Redraw
} }
} }
@ -176,12 +176,12 @@ impl Scrollable {
if self.current_index == new_index { if self.current_index == new_index {
WidgetEventResult::NoRedraw WidgetEventResult::NoRedraw
} else { } else {
self.update_index(new_index); self.set_index(new_index);
WidgetEventResult::Redraw WidgetEventResult::Redraw
} }
} }
pub fn update_num_items(&mut self, num_items: usize) { pub fn set_num_items(&mut self, num_items: usize) {
self.num_items = num_items; self.num_items = num_items;
if num_items <= self.current_index { if num_items <= self.current_index {

View File

@ -0,0 +1,76 @@
use crossterm::event::{KeyEvent, MouseEvent};
use tui::{backend::Backend, layout::Rect, widgets::Block, Frame};
use crate::{
app::{event::WidgetEventResult, text_table::SimpleColumn, Component, TextTable},
canvas::Painter,
};
use super::sort_text_table::SortableColumn;
/// A sortable, scrollable table with columns.
pub struct SortMenu {
/// The underlying table.
table: TextTable,
/// The bounds.
bounds: Rect,
}
impl SortMenu {
/// Creates a new [`SortMenu`].
pub fn new(num_columns: usize) -> Self {
let sort_menu_columns = vec![SimpleColumn::new_hard("Sort By".into(), None)];
let mut sort_menu = TextTable::new(sort_menu_columns);
sort_menu.set_num_items(num_columns);
Self {
table: sort_menu,
bounds: Default::default(),
}
}
/// Updates the index of the [`SortMenu`].
pub fn set_index(&mut self, index: usize) {
self.table.scrollable.set_index(index);
}
/// Returns the current index of the [`SortMenu`].
pub fn current_index(&mut self) -> usize {
self.table.scrollable.current_index()
}
/// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
pub fn draw_sort_menu<B: Backend, C: SortableColumn>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, columns: &[C], block: Block<'_>,
block_area: Rect,
) {
self.set_bounds(block_area);
let data = columns
.iter()
.map(|c| vec![(c.original_name().clone().into(), None, None)])
.collect::<Vec<_>>();
self.table
.draw_tui_table(painter, f, &data, block, block_area, true);
}
}
impl Component for SortMenu {
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, new_bounds: Rect) {
self.bounds = new_bounds;
}
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
self.table.handle_key_event(event)
}
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult {
self.table.handle_mouse_event(event)
}
}

View File

@ -257,9 +257,6 @@ where
/// The underlying [`TextTable`]. /// The underlying [`TextTable`].
pub table: TextTable<S>, pub table: TextTable<S>,
/// A corresponding "sort" menu.
pub sort_menu: TextTable,
} }
impl<S> SortableTextTable<S> impl<S> SortableTextTable<S>
@ -268,15 +265,9 @@ where
{ {
/// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty. /// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty.
pub fn new(columns: Vec<S>) -> Self { pub fn new(columns: Vec<S>) -> Self {
let sort_menu_columns = columns
.iter()
.map(|column| SimpleColumn::new_hard(column.original_name().clone(), None))
.collect::<Vec<_>>();
let mut st = Self { let mut st = Self {
sort_index: 0, sort_index: 0,
table: TextTable::new(columns), table: TextTable::new(columns),
sort_menu: TextTable::new(sort_menu_columns),
}; };
st.set_sort_index(0); st.set_sort_index(0);
st st
@ -296,11 +287,16 @@ where
self.table.current_scroll_index() self.table.current_scroll_index()
} }
/// Returns the current column. /// Returns the current column the table is sorting by.
pub fn current_column(&self) -> &S { pub fn current_sorting_column(&self) -> &S {
&self.table.columns[self.sort_index] &self.table.columns[self.sort_index]
} }
/// Returns the current column index the table is sorting by.
pub fn current_sorting_column_index(&self) -> usize {
self.sort_index
}
pub fn columns(&self) -> &[S] { pub fn columns(&self) -> &[S] {
&self.table.columns &self.table.columns
} }
@ -309,7 +305,7 @@ where
self.table.set_column(index, column) self.table.set_column(index, column)
} }
fn set_sort_index(&mut self, new_index: usize) { pub fn set_sort_index(&mut self, new_index: usize) {
if new_index == self.sort_index { if new_index == self.sort_index {
if let Some(column) = self.table.columns.get_mut(self.sort_index) { if let Some(column) = self.table.columns.get_mut(self.sort_index) {
match column.sorting_status() { match column.sorting_status() {
@ -356,12 +352,6 @@ where
self.table self.table
.draw_tui_table(painter, f, data, block, block_area, show_selected_entry); .draw_tui_table(painter, f, data, block, block_area, show_selected_entry);
} }
/// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
pub fn draw_sort_table<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, block: Block<'_>, block_area: Rect,
) {
}
} }
impl<S> Component for SortableTextTable<S> impl<S> Component for SortableTextTable<S>

View File

@ -173,6 +173,10 @@ where
self self
} }
pub fn columns(&self) -> &[C] {
&self.columns
}
fn displayed_column_names(&self) -> Vec<Cow<'static, str>> { fn displayed_column_names(&self) -> Vec<Cow<'static, str>> {
self.columns self.columns
.iter() .iter()
@ -181,7 +185,7 @@ where
} }
pub fn set_num_items(&mut self, num_items: usize) { pub fn set_num_items(&mut self, num_items: usize) {
self.scrollable.update_num_items(num_items); self.scrollable.set_num_items(num_items);
} }
pub fn set_column(&mut self, index: usize, column: C) { pub fn set_column(&mut self, index: usize, column: C) {

View File

@ -174,16 +174,12 @@ impl Widget for CpuGraph {
} }
}; };
// debug!("Area: {:?}", area);
let split_area = Layout::default() let split_area = Layout::default()
.margin(0) .margin(0)
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints(constraints) .constraints(constraints)
.split(area); .split(area);
// debug!("Split area: {:?}", split_area);
const Y_BOUNDS: [f64; 2] = [0.0, 100.5]; const Y_BOUNDS: [f64; 2] = [0.0, 100.5];
let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()]; let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()];

View File

@ -7,7 +7,7 @@ use unicode_segmentation::GraphemeCursor;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::Rect, layout::{Constraint, Direction, Layout, Rect},
widgets::{Block, Borders, TableState}, widgets::{Block, Borders, TableState},
Frame, Frame,
}; };
@ -15,7 +15,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
data_harvester::processes::ProcessHarvest, data_harvester::processes::ProcessHarvest,
event::{MultiKey, MultiKeyResult, WidgetEventResult}, event::{MultiKey, MultiKeyResult, ReturnSignal, WidgetEventResult},
query::*, query::*,
DataCollection, DataCollection,
}, },
@ -30,7 +30,7 @@ use super::{
sort_text_table::{SimpleSortableColumn, SortStatus, SortableColumn}, sort_text_table::{SimpleSortableColumn, SortStatus, SortableColumn},
text_table::TextTableData, text_table::TextTableData,
AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection, AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection,
SortableTextTable, TextInput, TextTable, Widget, SortMenu, SortableTextTable, TextInput, Widget,
}; };
/// AppSearchState deals with generic searching (I might do this in the future). /// AppSearchState deals with generic searching (I might do this in the future).
@ -836,11 +836,17 @@ impl SortableColumn for ProcessSortColumn {
} }
} }
enum ProcessSortState {
Shown,
Hidden,
}
/// A searchable, sortable table to manage processes. /// A searchable, sortable table to manage processes.
pub struct ProcessManager { pub struct ProcessManager {
bounds: Rect, bounds: Rect,
process_table: SortableTextTable<ProcessSortColumn>, process_table: SortableTextTable<ProcessSortColumn>,
sort_table: TextTable, sort_menu: SortMenu,
search_input: TextInput, search_input: TextInput,
dd_multi: MultiKey, dd_multi: MultiKey,
@ -848,7 +854,7 @@ pub struct ProcessManager {
selected: ProcessManagerSelection, selected: ProcessManagerSelection,
in_tree_mode: bool, in_tree_mode: bool,
show_sort: bool, // TODO: Add this for temp and disk??? sort_status: ProcessSortState,
show_search: bool, show_search: bool,
search_modifiers: SearchModifiers, search_modifiers: SearchModifiers,
@ -873,17 +879,15 @@ impl ProcessManager {
ProcessSortColumn::new(ProcessSortType::State), ProcessSortColumn::new(ProcessSortType::State),
]; ];
let process_table = SortableTextTable::new(process_table_columns).default_sort_index(2);
let mut manager = Self { let mut manager = Self {
bounds: Rect::default(), bounds: Rect::default(),
process_table, sort_menu: SortMenu::new(process_table_columns.len()),
sort_table: TextTable::new(vec![]), // TODO: Do this too process_table: SortableTextTable::new(process_table_columns).default_sort_index(2),
search_input: TextInput::new(), search_input: TextInput::new(),
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static... dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
selected: ProcessManagerSelection::Processes, selected: ProcessManagerSelection::Processes,
in_tree_mode: false, in_tree_mode: false,
show_sort: false, sort_status: ProcessSortState::Hidden,
show_search: false, show_search: false,
search_modifiers: SearchModifiers::default(), search_modifiers: SearchModifiers::default(),
display_data: Default::default(), display_data: Default::default(),
@ -911,7 +915,9 @@ impl ProcessManager {
if let ProcessManagerSelection::Sort = self.selected { if let ProcessManagerSelection::Sort = self.selected {
WidgetEventResult::NoRedraw WidgetEventResult::NoRedraw
} else { } else {
self.show_sort = true; self.sort_menu
.set_index(self.process_table.current_sorting_column_index());
self.sort_status = ProcessSortState::Shown;
self.selected = ProcessManagerSelection::Sort; self.selected = ProcessManagerSelection::Sort;
WidgetEventResult::Redraw WidgetEventResult::Redraw
} }
@ -945,6 +951,20 @@ impl Component for ProcessManager {
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
// "Global" handling:
match event.code {
KeyCode::Esc => {
if let ProcessSortState::Shown = self.sort_status {
self.sort_status = ProcessSortState::Hidden;
if let ProcessManagerSelection::Sort = self.selected {
self.selected = ProcessManagerSelection::Processes;
}
return WidgetEventResult::Redraw;
}
}
_ => {}
}
match self.selected { match self.selected {
ProcessManagerSelection::Processes => { ProcessManagerSelection::Processes => {
// Try to catch some stuff first... // Try to catch some stuff first...
@ -982,7 +1002,7 @@ impl Component for ProcessManager {
self.in_tree_mode = !self.in_tree_mode; self.in_tree_mode = !self.in_tree_mode;
return WidgetEventResult::Redraw; return WidgetEventResult::Redraw;
} }
KeyCode::F(6) => { KeyCode::Char('s') | KeyCode::F(6) => {
return self.open_sort(); return self.open_sort();
} }
KeyCode::F(9) => { KeyCode::F(9) => {
@ -1003,6 +1023,18 @@ impl Component for ProcessManager {
self.process_table.handle_key_event(event) self.process_table.handle_key_event(event)
} }
ProcessManagerSelection::Sort => { ProcessManagerSelection::Sort => {
match event.code {
KeyCode::Enter if event.modifiers.is_empty() => {
self.process_table
.set_sort_index(self.sort_menu.current_index());
return WidgetEventResult::Signal(ReturnSignal::Update);
}
_ => {}
}
self.sort_menu.handle_key_event(event)
}
ProcessManagerSelection::Search => {
if event.modifiers.is_empty() { if event.modifiers.is_empty() {
match event.code { match event.code {
KeyCode::F(1) => {} KeyCode::F(1) => {}
@ -1019,9 +1051,8 @@ impl Component for ProcessManager {
} }
} }
self.sort_table.handle_key_event(event) self.search_input.handle_key_event(event)
} }
ProcessManagerSelection::Search => self.search_input.handle_key_event(event),
} }
} }
@ -1041,12 +1072,12 @@ impl Component for ProcessManager {
WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s), WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s),
} }
} }
} else if self.sort_table.does_border_intersect_mouse(&event) { } else if self.sort_menu.does_border_intersect_mouse(&event) {
if let ProcessManagerSelection::Sort = self.selected { if let ProcessManagerSelection::Sort = self.selected {
self.sort_table.handle_mouse_event(event) self.sort_menu.handle_mouse_event(event)
} else { } else {
self.selected = ProcessManagerSelection::Sort; self.selected = ProcessManagerSelection::Sort;
self.sort_table.handle_mouse_event(event); self.sort_menu.handle_mouse_event(event);
WidgetEventResult::Redraw WidgetEventResult::Redraw
} }
} else if self.search_input.does_border_intersect_mouse(&event) { } else if self.search_input.does_border_intersect_mouse(&event) {
@ -1063,7 +1094,7 @@ impl Component for ProcessManager {
} }
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected { MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected {
ProcessManagerSelection::Processes => self.process_table.handle_mouse_event(event), ProcessManagerSelection::Processes => self.process_table.handle_mouse_event(event),
ProcessManagerSelection::Sort => self.sort_table.handle_mouse_event(event), ProcessManagerSelection::Sort => self.sort_menu.handle_mouse_event(event),
ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event), ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event),
}, },
_ => WidgetEventResult::NoRedraw, _ => WidgetEventResult::NoRedraw,
@ -1079,16 +1110,76 @@ impl Widget for ProcessManager {
fn draw<B: Backend>( fn draw<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool, &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
) { ) {
let block = Block::default() match self.sort_status {
.border_style(if selected { ProcessSortState::Shown => {
painter.colours.highlighted_border_style const SORT_CONSTRAINTS: [Constraint; 2] =
} else { [Constraint::Length(10), Constraint::Min(0)];
painter.colours.border_style
})
.borders(Borders::ALL);
self.process_table let split_area = Layout::default()
.draw_tui_table(painter, f, &self.display_data, block, area, selected); .margin(0)
.direction(Direction::Horizontal)
.constraints(SORT_CONSTRAINTS)
.split(area);
let sort_block = Block::default()
.border_style(if selected {
if let ProcessManagerSelection::Sort = self.selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
}
} else {
painter.colours.border_style
})
.borders(Borders::ALL);
self.sort_menu.draw_sort_menu(
painter,
f,
self.process_table.columns(),
sort_block,
split_area[0],
);
let process_block = Block::default()
.border_style(if selected {
if let ProcessManagerSelection::Processes = self.selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
}
} else {
painter.colours.border_style
})
.borders(Borders::ALL);
self.process_table.draw_tui_table(
painter,
f,
&self.display_data,
process_block,
split_area[1],
selected,
);
}
ProcessSortState::Hidden => {
let block = Block::default()
.border_style(if selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
})
.borders(Borders::ALL);
self.process_table.draw_tui_table(
painter,
f,
&self.display_data,
block,
area,
selected,
);
}
}
} }
fn update_data(&mut self, data_collection: &DataCollection) { fn update_data(&mut self, data_collection: &DataCollection) {
@ -1099,58 +1190,60 @@ impl Widget for ProcessManager {
// TODO: Filtering // TODO: Filtering
true true
}) })
.sorted_by(match self.process_table.current_column().sort_type { .sorted_by(
ProcessSortType::Pid => { match self.process_table.current_sorting_column().sort_type {
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid) ProcessSortType::Pid => {
} |a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
ProcessSortType::Count => {
todo!()
}
ProcessSortType::Name => {
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
}
ProcessSortType::Command => {
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
}
ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
},
ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
},
ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
},
ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
},
ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
},
ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.total_read_bytes.cmp(&b.total_read_bytes)
},
ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.total_write_bytes.cmp(&b.total_write_bytes)
},
ProcessSortType::User => {
#[cfg(target_family = "unix")]
{
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
} }
#[cfg(not(target_family = "unix"))] ProcessSortType::Count => {
{ todo!()
|_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
} }
} ProcessSortType::Name => {
ProcessSortType::State => { |a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.process_state.cmp(&b.process_state) }
} ProcessSortType::Command => {
}); |a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
}
ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
},
ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
},
ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
},
ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
},
ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
},
ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.total_read_bytes.cmp(&b.total_read_bytes)
},
ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.total_write_bytes.cmp(&b.total_write_bytes)
},
ProcessSortType::User => {
#[cfg(target_family = "unix")]
{
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
}
#[cfg(not(target_family = "unix"))]
{
|_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
}
}
ProcessSortType::State => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.process_state.cmp(&b.process_state)
},
},
);
self.display_data = if let SortStatus::SortDescending = self self.display_data = if let SortStatus::SortDescending = self
.process_table .process_table
.current_column() .current_sorting_column()
.sortable_column .sortable_column
.sorting_status() .sorting_status()
{ {

View File

@ -4,7 +4,7 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use bottom::{app::event::EventResult, canvas, constants::*, options::*, *}; use bottom::{app::event::EventResult, canvas, options::*, *};
use std::{ use std::{
boxed::Box, boxed::Box,

View File

@ -359,16 +359,41 @@ impl Painter {
if let Some(layout_node) = arena.get(node).map(|n| n.get()) { if let Some(layout_node) = arena.get(node).map(|n| n.get()) {
match layout_node { match layout_node {
LayoutNode::Row(row) => { LayoutNode::Row(row) => {
let split_area = Layout::default() let split_area = {
.margin(0) let initial_split = Layout::default()
.direction(Direction::Horizontal) .margin(0)
.constraints(row.constraints.clone()) .direction(Direction::Horizontal)
.split(area); .constraints(row.constraints.clone())
.split(area);
// debug!( if initial_split.is_empty() {
// "Row - constraints: {:#?}, split_area: {:#?}", vec![]
// row.constraints, split_area } else {
// ); let mut checked_split =
Vec::with_capacity(initial_split.len());
let mut right_value = initial_split[0].right();
checked_split.push(initial_split[0]);
for rect in initial_split[1..].iter() {
if right_value == rect.left() {
right_value = rect.right();
checked_split.push(*rect);
} else {
let diff = rect.x.saturating_sub(right_value);
let new_rect = Rect::new(
right_value,
rect.y,
rect.width + diff,
rect.height,
);
right_value = new_rect.right();
checked_split.push(new_rect);
}
}
checked_split
}
};
for (child, child_area) in node.children(arena).zip(split_area) { for (child, child_area) in node.children(arena).zip(split_area) {
traverse_and_draw_tree( traverse_and_draw_tree(
@ -384,16 +409,41 @@ impl Painter {
} }
} }
LayoutNode::Col(col) => { LayoutNode::Col(col) => {
let split_area = Layout::default() let split_area = {
.margin(0) let initial_split = Layout::default()
.direction(Direction::Vertical) .margin(0)
.constraints(col.constraints.clone()) .direction(Direction::Vertical)
.split(area); .constraints(col.constraints.clone())
.split(area);
// debug!( if initial_split.is_empty() {
// "Col - constraints: {:#?}, split_area: {:#?}", vec![]
// col.constraints, split_area } else {
// ); let mut checked_split =
Vec::with_capacity(initial_split.len());
let mut bottom_value = initial_split[0].bottom();
checked_split.push(initial_split[0]);
for rect in initial_split[1..].iter() {
if bottom_value == rect.top() {
bottom_value = rect.bottom();
checked_split.push(*rect);
} else {
let diff = rect.y.saturating_sub(bottom_value);
let new_rect = Rect::new(
rect.x,
bottom_value,
rect.width,
rect.height + diff,
);
bottom_value = new_rect.bottom();
checked_split.push(new_rect);
}
}
checked_split
}
};
for (child, child_area) in node.children(arena).zip(split_area) { for (child, child_area) in node.children(arena).zip(split_area) {
traverse_and_draw_tree( traverse_and_draw_tree(
@ -409,8 +459,6 @@ impl Painter {
} }
} }
LayoutNode::Widget => { LayoutNode::Widget => {
// debug!("Widget - area: {:#?}", area);
if let Some(widget) = lookup_map.get_mut(&node) { if let Some(widget) = lookup_map.get_mut(&node) {
widget.set_bounds(area); widget.set_bounds(area);
widget.draw(painter, f, area, selected_id == node); widget.draw(painter, f, area, selected_id == node);