use std::fmt::Write as _;
use std::time::Duration;
use qframe::color::Rgb;
use qframe::icons::GlyphMode;
use qframe::runtime::{App, Command, Harness};
use qframe::widget::View;
use qframe::widgets::{Spinner, SpinnerStyle};
const THEMES: [&str; 4] = ["monochrome", "iris", "nordic", "amber"];
const MODES: [GlyphMode; 3] = [GlyphMode::Nerd, GlyphMode::Unicode, GlyphMode::Ascii];
struct One {
style: SpinnerStyle,
variant: Option<&'static str>,
done: bool,
}
impl App for One {
type Msg = bool;
fn update(&mut self, done: bool) -> Command<bool> {
self.done = done;
Command::none()
}
fn view(&self, ui: &mut View<'_, bool>) {
let mut spinner = Spinner::new().style(self.style).done(self.done);
if let Some(variant) = self.variant {
spinner = spinner.variant(variant);
}
ui.add(spinner);
}
}
fn hex(color: Option<Rgb>) -> String {
color.map_or_else(|| "none".to_owned(), |c| format!("{:02x}{:02x}{:02x}", c.r, c.g, c.b))
}
fn fnv(text: &str) -> u64 {
text.bytes().fold(0xcbf2_9ce4_8422_2325, |hash, byte| (hash ^ u64::from(byte)).wrapping_mul(0x0100_0000_01b3))
}
fn sample(h: &mut Harness<One>, samples: usize) -> (String, String) {
let (mut glyphs, mut colors) = (String::new(), String::new());
for _ in 0..samples {
glyphs.push(h.screen().chars().next().unwrap_or(' '));
colors.push_str(&hex(h.fg(0, 0)));
colors.push(' ');
h.advance(Duration::from_millis(20));
}
(glyphs, colors)
}
fn line(out: &mut String, case: &str, (glyphs, colors): &(String, String)) {
let first: Vec<&str> = colors.split(' ').take(3).collect();
let _ = writeln!(out, "{case}: {glyphs} | {} | {:016x}", first.join(" "), fnv(colors));
}
fn harness(one: One, theme: &str, mode: GlyphMode) -> Harness<One> {
let mut h = Harness::new(one, 4, 1);
h.set_theme(theme).set_glyph_mode(mode);
h
}
fn record() -> String {
let mut out = String::new();
for theme in THEMES {
for mode in MODES {
for style in SpinnerStyle::ALL {
for variant in [None, Some("success")] {
let case = format!("{theme} {mode:?} {} {}", style.name(), variant.unwrap_or("-"));
let mut h = harness(One { style, variant, done: false }, theme, mode);
line(&mut out, &format!("{case} turning"), &sample(&mut h, 161));
let mut h = harness(One { style, variant, done: false }, theme, mode);
h.set_reduced_motion(true);
line(&mut out, &format!("{case} reduced"), &sample(&mut h, 3));
h.send(true);
line(&mut out, &format!("{case} reduced-done"), &sample(&mut h, 3));
let mut h = harness(One { style, variant, done: false }, theme, mode);
h.advance(Duration::from_millis(310));
h.send(true);
line(&mut out, &format!("{case} done"), &sample(&mut h, 20));
let mut h = harness(One { style, variant, done: true }, theme, mode);
line(&mut out, &format!("{case} drawn-done"), &sample(&mut h, 3));
}
}
}
}
out
}
#[test]
fn every_spinner_draws_the_frames_and_colours_it_drew_before_animations() {
let recorded = record();
let pinned = include_str!("fixtures/spinner-sequences.txt");
for (now, before) in recorded.lines().zip(pinned.lines()) {
assert_eq!(now, before);
}
assert_eq!(recorded.lines().count(), pinned.lines().count());
}