use std::marker::PhantomData;
use ratatui::buffer::Buffer;
use ratatui::layout::Constraint;
use ratatui::layout::Layout;
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::style::Style;
use ratatui::style::Stylize;
use ratatui::symbols;
use ratatui::text::Line;
use ratatui::text::Text;
use ratatui::widgets::Block;
use ratatui::widgets::Borders;
use ratatui::widgets::Clear;
use ratatui::widgets::HighlightSpacing;
use ratatui::widgets::List;
use ratatui::widgets::StatefulWidget;
use ratatui::widgets::Widget;
use super::ListItem;
use super::ListState;
pub struct ListWidget<T>
{
active: bool,
style: Style,
header_style: Style,
header_centered: bool,
alternate_background: Option<Color>,
highlight_style: Style,
highlight_symbol: String,
ds: PhantomData<T>,
}
impl<T> ListWidget<T>
{
pub fn new(active: bool) -> Self
{
Self {
active,
style: Style::default(),
header_style: Style::default(),
header_centered: false,
alternate_background: None,
highlight_style: Style::default(),
highlight_symbol: ">".to_string(),
ds: PhantomData,
}
}
pub fn style<S: Into<Style>>(
mut self,
style: S,
) -> Self
{
self.style = style.into();
self
}
pub fn set_header_style<S: Into<Style>>(
&mut self,
style: S,
)
{
self.header_style = style.into();
}
pub fn with_header_style<S: Into<Style>>(
mut self,
style: S,
) -> Self
{
self.set_header_style(style);
self
}
pub fn header_style(&self) -> Style
{
self.header_style
}
pub fn set_header_centered(
&mut self,
centered: bool,
)
{
self.header_centered = centered;
}
pub fn with_header_centered(mut self) -> Self
{
self.set_header_centered(true);
self
}
pub fn header_centered(&self) -> bool
{
self.header_centered
}
pub fn set_alternate_background(
&mut self,
color: Color,
)
{
self.alternate_background = Some(color);
}
pub fn with_alternate_background(
mut self,
color: Color,
) -> Self
{
self.set_alternate_background(color);
self
}
pub fn clear_alternate_background(&mut self)
{
self.alternate_background = None;
}
pub fn alternate_background(&self) -> Option<Color>
{
self.alternate_background
}
fn get_alternate_color(
&self,
i: usize,
) -> Option<Color>
{
if let Some(color) = self.alternate_background
{
if i % 2 == 0
{
self.style
.bg
}
else
{
Some(color)
}
}
else
{
self.style
.bg
}
}
pub fn set_highlight_style<S: Into<Style>>(
&mut self,
style: S,
)
{
self.highlight_style = style.into();
}
pub fn with_highlight_style<S: Into<Style>>(
mut self,
style: S,
) -> Self
{
self.set_highlight_style(style);
self
}
pub fn highlight_style(&self) -> Style
{
self.highlight_style
}
pub fn set_highlight_symbol<S>(
&mut self,
symbol: S,
) where
S: AsRef<str>,
{
self.highlight_symbol = symbol
.as_ref()
.to_string();
}
pub fn with_highlight_symbol<S>(
mut self,
symbol: S,
) -> Self
where
S: AsRef<str>,
{
self.set_highlight_symbol(symbol);
self
}
pub fn highlight_symbol(&self) -> &String
{
&self.highlight_symbol
}
}
impl<T> StatefulWidget for ListWidget<T>
where
T: ListItem,
{
type State = ListState<T>;
fn render(
self,
area: Rect,
buf: &mut Buffer,
state: &mut Self::State,
)
{
Clear.render(
area, buf,
);
let mut block = Block::new().style(self.style);
if let Some(title) = state
.title
.as_ref()
{
let mut line = Line::raw(title);
if self.header_centered
{
line = line.centered();
}
block = block
.borders(Borders::TOP)
.border_set(symbols::border::EMPTY)
.border_style(self.header_style)
.title(line);
}
let items = state
.items
.iter()
.filter(
|i| {
i.filter(
state
.filter
.as_ref(),
)
},
)
.enumerate()
.map(
|(i, s)| {
let item = s.to_listitem(
area.width,
i,
self.active,
);
if let Some(color) = self.get_alternate_color(i)
{
item.bg(color)
}
else
{
item
}
},
)
.collect::<Vec<_>>();
let list = List::new(items)
.block(block)
.highlight_style(self.highlight_style)
.highlight_symbol(&self.highlight_symbol)
.highlight_spacing(HighlightSpacing::Always);
let mut area = area;
if state.has_scrollbar() && area.height > 2
{
area.width -= 1;
}
StatefulWidget::render(
list,
area,
buf,
&mut state.state,
);
if state.has_scrollbar()
{
let mut scrollbar_area = area;
scrollbar_area.x += area.width;
scrollbar_area.width = 1;
if scrollbar_area.height >= 3
{
let first_char = if state
.state
.offset()
== 0
{
" "
}
else
{
"▴"
};
let last_char = if state
.state
.offset()
+ (scrollbar_area.height as usize)
>= state
.items
.len()
{
" "
}
else
{
"▾"
};
let [first_area, inner_area, last_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
Constraint::Length(1),
])
.areas(scrollbar_area);
Text::styled(
first_char,
Style::new().bg(
self.style
.bg
.unwrap_or_default(),
),
)
.render(
first_area, buf,
);
Block::new()
.style(
Style::new().bg(
self.style
.bg
.unwrap_or_default(),
),
)
.render(
inner_area, buf,
);
Text::styled(
last_char,
Style::new().bg(
self.style
.bg
.unwrap_or_default(),
),
)
.render(
last_area, buf,
);
}
}
}
}