use std::collections::VecDeque;
use super::ActiveBlock;
use tui::{
backend::Backend,
layout::Rect,
style::Style,
text::Span,
widgets::{Block, List, ListItem, ListState, TableState},
Frame,
};
#[derive(Clone)]
pub struct StatefulTable<T> {
pub state: TableState,
pub items: Vec<T>,
}
impl<T> StatefulTable<T> {
pub fn new() -> StatefulTable<T> {
StatefulTable {
state: TableState::default(),
items: Vec::new(),
}
}
pub fn with_items(items: Vec<T>) -> StatefulTable<T> {
let mut sft = StatefulTable::new();
sft.set_items(items);
sft
}
pub fn set_items(&mut self, items: Vec<T>) {
self.items = items;
if !self.items.is_empty() {
let i = self
.state
.selected()
.map_or(0, |i| if i > 0 { i } else { 0 });
self.state.select(Some(i));
}
}
pub fn next(&mut self) {
let i = self.state.selected().map_or(0, |i| {
if i >= self.items.len().wrapping_sub(1) {
0
} else {
i + 1
}
});
self.state.select(Some(i));
}
pub fn previous(&mut self) {
let i = self.state.selected().map_or(0, |i| {
if i == 0 {
self.items.len().wrapping_sub(1)
} else {
i - 1
}
});
self.state.select(Some(i));
}
pub fn _unselect(&mut self) {
self.state.select(None);
}
}
impl<T: Clone> StatefulTable<T> {
pub fn get_selected_item(&self) -> Option<T> {
if !self.items.is_empty() {
self.state.selected().map(|i| self.items[i].clone())
} else {
None
}
}
}
pub struct TabsState {
pub titles: Vec<String>,
pub index: usize,
pub active_block_ids: Option<Vec<ActiveBlock>>,
pub active_block: Option<ActiveBlock>,
}
impl TabsState {
pub fn new(titles: Vec<String>) -> TabsState {
TabsState {
titles,
index: 0,
active_block_ids: None,
active_block: None,
}
}
pub fn with_active_blocks(titles: Vec<String>, blocks: Vec<ActiveBlock>) -> TabsState {
TabsState {
titles,
index: 0,
active_block: Some(blocks[0]),
active_block_ids: Some(blocks),
}
}
pub fn set_index(&mut self, index: usize) {
self.index = index;
self.set_active();
}
pub fn set_active(&mut self) {
self.active_block = self.active_block_ids.as_ref().map(|ids| ids[self.index]);
}
pub fn next(&mut self) {
self.index = (self.index + 1) % self.titles.len();
self.set_active();
}
pub fn previous(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.titles.len() - 1;
}
self.set_active();
}
}
pub struct ScrollableTxt {
items: Vec<String>,
pub offset: u16,
}
impl ScrollableTxt {
pub fn new() -> ScrollableTxt {
ScrollableTxt {
items: vec![],
offset: 0,
}
}
pub fn with_string(item: String) -> ScrollableTxt {
let items: Vec<&str> = item.split('\n').collect();
let items: Vec<String> = items.iter().map(|it| it.to_string()).collect();
ScrollableTxt { items, offset: 0 }
}
pub fn get_txt(&self) -> String {
self.items.join("\n")
}
pub fn scroll_down(&mut self) {
if self.offset < (self.items.len() - 8) as u16 {
self.offset += 1;
}
}
pub fn scroll_up(&mut self) {
if self.offset > 0 {
self.offset -= 1;
}
}
}
#[derive(Debug, Clone)]
pub struct LogsState {
#[allow(clippy::type_complexity)]
records: VecDeque<(String, Option<(Vec<ListItem<'static>>, u16)>)>,
wrapped_length: usize,
pub state: ListState,
pub id: String,
}
impl LogsState {
pub fn new(id: String) -> LogsState {
LogsState {
records: VecDeque::with_capacity(512),
state: ListState::default(),
wrapped_length: 0,
id,
}
}
pub fn get_plain_text(&self) -> String {
self.records.iter().fold(String::new(), |mut acc, v| {
acc.push('\n');
acc.push_str(v.0.as_str());
acc
})
}
pub fn scroll_down(&mut self) {
let i = self.state.selected().map_or(0, |i| {
if i >= self.wrapped_length.wrapping_sub(1) {
i
} else {
i + 1
}
});
self.state.select(Some(i));
}
pub fn scroll_up(&mut self) {
let i = self
.state
.selected()
.map_or(0, |i| if i != 0 { i - 1 } else { 0 });
self.state.select(Some(i));
}
pub fn render_list<B: Backend>(
&mut self,
f: &mut Frame<B>,
logs_area: Rect,
block: Block,
style: Style,
follow: bool,
) {
let available_lines = logs_area.height as usize;
let logs_area_width = logs_area.width as usize;
let num_records = self.records.len();
let mut wrapped_lines_len = 0;
let mut items = Vec::with_capacity(logs_area.height as usize);
let lines_to_skip = if follow {
self.unselect();
num_records.saturating_sub(available_lines)
} else {
0
};
items.extend(
self
.records
.iter_mut()
.skip(lines_to_skip)
.map(|r| {
if let Some(wrapped) = &r.1 {
if wrapped.1 as usize == logs_area_width {
wrapped_lines_len += wrapped.0.len();
return wrapped.0.clone();
}
}
r.1 = Some((
textwrap::wrap(r.0.as_ref(), logs_area_width)
.into_iter()
.map(|s| s.to_string())
.map(|c| Span::styled(c, style))
.map(ListItem::new)
.collect::<Vec<ListItem>>(),
logs_area.width,
));
wrapped_lines_len += r.1.as_ref().unwrap().0.len();
r.1.as_ref().unwrap().0.clone()
})
.flatten(),
);
let lines_to_skip = if follow {
wrapped_lines_len.saturating_sub(available_lines)
} else {
0
};
let items = items
.into_iter()
.skip(lines_to_skip)
.collect::<Vec<_>>();
self.wrapped_length = items.len();
let list = List::new(items).block(block);
f.render_stateful_widget(list, logs_area, &mut self.state);
}
pub fn add_record(&mut self, record: String) {
self.records.push_back((record, None));
}
fn unselect(&mut self) {
self.state.select(None);
}
}