use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
text::{Line, Span},
widgets::{Paragraph, StatefulWidget, Widget},
};
use crate::scroll_list::ScrollListState;
use crate::{CHECK, CROSS, DEFAULT_FRAMES};
#[derive(Clone)]
pub struct SpinState {
pub frame: usize,
pub finished: bool,
pub exit_code: Option<i32>,
pub scroll_list: ScrollListState,
}
impl Default for SpinState {
fn default() -> Self {
Self {
frame: 0,
finished: false,
exit_code: None,
scroll_list: ScrollListState::default(),
}
}
}
impl SpinState {
pub fn tick(&mut self, frames_len: usize) {
if !self.finished && frames_len > 0 {
self.frame = (self.frame + 1) % frames_len;
}
}
pub fn output_next(&mut self, line_count: usize) {
self.scroll_list.next(line_count);
}
pub fn output_previous(&mut self) {
self.scroll_list.previous();
}
pub fn output_last(&mut self, line_count: usize) {
self.scroll_list.last(line_count);
}
}
#[derive(Clone)]
pub struct Spin {
frames: &'static [&'static str],
spinner_style: Style,
}
impl Spin {
pub fn new() -> Self {
Self {
frames: DEFAULT_FRAMES,
spinner_style: Style::new().fg(Color::Cyan),
}
}
pub fn frames(mut self, frames: &'static [&'static str]) -> Self {
self.frames = frames;
self
}
pub fn spinner_style(mut self, style: Style) -> Self {
self.spinner_style = style;
self
}
}
impl Default for Spin {
fn default() -> Self {
Self::new()
}
}
impl StatefulWidget for Spin {
type State = SpinState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let (ch, style) = if state.finished {
let ok = state.exit_code.map_or(true, |c| c == 0);
(
if ok { CHECK } else { CROSS },
Style::new().fg(if ok { Color::Green } else { Color::Red }),
)
} else {
let frame = if self.frames.is_empty() {
" "
} else {
self.frames[state.frame % self.frames.len()]
};
(frame, self.spinner_style)
};
Paragraph::new(Line::from(Span::styled(ch.to_string(), style))).render(area, buf);
}
}
#[cfg(test)]
mod tests;