Files
gitnow/crates/gitnow/src/components/spinner.rs
kjuulh 708f8d6388
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
feat: add ability to specify custom command
2025-01-08 22:32:39 +01:00

87 lines
1.9 KiB
Rust

use std::time::{Duration, Instant};
use ratatui::{
text::{Line, Span},
widgets::{Block, Paragraph, StatefulWidget, Widget},
};
use super::{BatchCommand, IntoCommand, Msg};
pub struct Spinner<'a> {
span: Span<'a>,
block: Option<Block<'a>>,
}
impl<'a> Spinner<'a> {
pub fn new(span: Span<'a>) -> Self {
Self { span, block: None }
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}
}
impl StatefulWidget for Spinner<'_> {
type State = SpinnerState;
fn render(
self,
area: ratatui::prelude::Rect,
buf: &mut ratatui::prelude::Buffer,
state: &mut Self::State,
) {
let frame = MINIDOT_FRAMES
.get((state.frame) % MINIDOT_FRAMES.len())
.expect("to find a valid static frame");
let line = Line::from(vec![Span::from(*frame), Span::from(" "), self.span]);
let para = Paragraph::new(vec![line]);
let para = if let Some(block) = self.block {
para.block(block)
} else {
para
};
para.render(area, buf)
}
}
pub struct SpinnerState {
last_event: Instant,
interval: Duration,
frame: usize,
}
impl Default for SpinnerState {
fn default() -> Self {
Self {
last_event: Instant::now(),
interval: Duration::from_millis(1000 / 12),
frame: 0,
}
}
}
const MINIDOT_FRAMES: [&str; 10] = ["", "", "", "", "", "", "", "", "", ""];
impl SpinnerState {
pub fn update(&mut self, _msg: &Msg) -> impl IntoCommand {
let batch = BatchCommand::default();
let now = Instant::now();
if now.duration_since(self.last_event) >= self.interval {
self.last_event = now;
self.next_state();
}
batch
}
fn next_state(&mut self) {
self.frame = self.frame.wrapping_add(1);
}
}