use std::{io, time::Duration};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use scrin::{
PresentStrategy, Terminal, TerminalOptions,
effects::{EffectKind, LoaderKind},
layout::{Constraint, Direction, Layout},
widgets::{Clear, Widget},
};
use scrin_widgets::{
AislingExt, AislingPalette, Gauge, Paragraph, ScrinEffect, ScrinLoader, SignalPanel, StatusBar,
};
fn main() -> io::Result<()> {
let mut terminal = Terminal::init_with(TerminalOptions {
mouse_capture: true,
bracketed_paste: true,
..TerminalOptions::default()
})?;
let result = run(&mut terminal);
terminal.restore()?;
result
}
fn run(terminal: &mut Terminal) -> io::Result<()> {
let mut tick = 0_u64;
let mut effect_index = 0_usize;
let mut loader_index = 0_usize;
loop {
let effects = EffectKind::all();
let loaders = LoaderKind::all();
let effect = effects[effect_index % effects.len()];
let loader = loaders[loader_index % loaders.len()];
let progress = wave_ratio(tick, 160, 0) as f32;
terminal.draw_with_present_strategy(PresentStrategy::MarkedDirty, |frame| {
let palette = match effect_index % 3 {
0 => AislingPalette::cypherpunk(),
1 => AislingPalette::dream(),
_ => AislingPalette::flare(),
};
frame.render_widget_timed("clear", Clear::with_bg(palette.shadow), frame.area());
frame.mark_dirty(frame.area());
let root = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(5),
Constraint::Min(10),
Constraint::Length(7),
Constraint::Length(1),
])
.split(frame.area());
let header = palette.block("Scrin effects and loaders as widgets");
let header_inner = header.inner(root[0]);
frame.render_widget_timed("header:block", header, root[0]);
frame.render_widget_timed(
"header:text",
Paragraph::new(format!(
"EffectPlayer -> ScrinEffect | LoaderPlayer -> ScrinLoader\neffect: {} ({}/{}) | loader: {} ({}/{})",
effect.name(),
effect_index % effects.len() + 1,
effects.len(),
loader.name(),
loader_index % loaders.len() + 1,
loaders.len()
))
.palette(palette)
.aisling()
.tick(tick)
.intensity(5),
header_inner,
);
frame.mark_dirty(root[0]);
ScrinEffect::new(effect, "scrin-widgets")
.tick(tick)
.duration(48)
.seed(13 + effect_index as u64)
.palette(palette)
.block(palette.block(effect.name()))
.render_with_interaction(frame, "effects:selected", root[1]);
let lower = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(42),
Constraint::Percentage(28),
Constraint::Percentage(30),
])
.split(root[2]);
ScrinLoader::new(loader, progress)
.tick(tick)
.label(loader.name())
.unit("units")
.fraction(true)
.palette(palette)
.block(palette.block("loader"))
.render_with_interaction(frame, "loaders:selected", lower[0]);
Gauge::new(f64::from(progress))
.label(format!("progress {:>3}%", (progress * 100.0) as u16))
.palette(palette)
.block(palette.block("flow"))
.render(frame.buffer(), lower[1]);
frame.mark_dirty(lower[1]);
SignalPanel::new("controls")
.line("Tab: next effect")
.line("Shift-Tab: prev effect")
.line("[: prev loader")
.line("]: next loader")
.tick(tick)
.palette(palette)
.render(frame.buffer(), lower[2]);
frame.mark_dirty(lower[2]);
frame.render_widget_timed(
"status",
StatusBar::new()
.left(format!("frame {}", frame.frame_count()))
.center(format!("dirty regions {}", frame.dirty_regions().len()))
.right("q quits")
.palette(palette),
root[3],
);
frame.mark_dirty(root[3]);
})?;
if event::poll(Duration::from_millis(36))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Tab => effect_index = (effect_index + 1) % EffectKind::all().len(),
KeyCode::BackTab => {
let total = EffectKind::all().len();
effect_index = (effect_index + total - 1) % total;
}
KeyCode::Char(']') => {
loader_index = (loader_index + 1) % LoaderKind::all().len()
}
KeyCode::Char('[') => {
let total = LoaderKind::all().len();
loader_index = (loader_index + total - 1) % total;
}
_ => {}
}
}
}
}
tick = tick.wrapping_add(1);
}
Ok(())
}
fn wave_ratio(tick: u64, period: u64, offset: u64) -> f64 {
let phase = ((tick + offset) % period) as f64 / period as f64;
1.0 - (phase - 0.5).abs() * 2.0
}