use std::{io, time::Duration};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use scrin::{
Color, Terminal,
layout::{Constraint, Direction, Layout},
style::Style,
widgets::{
Paragraph, Widget,
block::{Block, BorderStyle},
},
};
use scrin_widgets::{AislingExt, AislingPalette, GlyphRain, NebulaGauge, SignalPanel};
fn main() -> io::Result<()> {
let mut terminal = Terminal::init()?;
let result = run(&mut terminal);
terminal.restore()?;
result
}
fn run(terminal: &mut Terminal) -> io::Result<()> {
let forge = AislingPalette {
low: Color::rgb(255, 111, 63),
mid: Color::rgb(255, 183, 77),
high: Color::rgb(255, 248, 194),
pulse: Color::rgb(93, 210, 255),
shadow: Color::rgb(39, 19, 10),
};
let frost = AislingPalette {
low: Color::rgb(72, 197, 255),
mid: Color::rgb(117, 124, 255),
high: Color::rgb(224, 244, 255),
pulse: Color::rgb(170, 255, 232),
shadow: Color::rgb(9, 15, 30),
};
let mut tick = 211_u64;
loop {
terminal.draw(|frame| {
let root = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(7),
Constraint::Min(10),
Constraint::Length(8),
])
.split(frame.area());
let buffer = frame.buffer();
GlyphRain::new(tick)
.density(24 + (tick % 20) as u16)
.glyphs("*:.+xX#@")
.palette(frost)
.block(color_block("cold starfield", frost.mid))
.render(buffer, root[0]);
let middle = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(30),
Constraint::Percentage(40),
Constraint::Percentage(30),
])
.split(root[1]);
SignalPanel::new("forge left")
.line("bellows: awake")
.line("ore: glass-gold")
.line("gravity: braided")
.line("tongs: singing")
.tick(tick)
.palette(forge)
.render(buffer, middle[0]);
let core = color_block("helium anvil", forge.high);
let core_inner = core.inner(middle[1]);
core.render(buffer, middle[1]);
Paragraph::new(" STARFORGE\n\n plasma threads folded into terminal glass\n border light behaves like hot metal\n gauges breathe pressure while glyphs drift cold\n press q or Esc to bank the furnace")
.with_style(Style::default().fg(forge.high).bold())
.with_word_wrap(true)
.aisling()
.tick(tick)
.palette(forge)
.intensity(9)
.render(buffer, core_inner);
SignalPanel::new("forge right")
.line("quench: blue")
.line("runes: aligned")
.line("shell: intact")
.line("output: crown")
.tick(tick + 17)
.palette(frost)
.render(buffer, middle[2]);
let lower = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(33),
Constraint::Percentage(34),
Constraint::Percentage(33),
])
.split(root[2]);
let crown = wave_ratio(tick, 96, 0);
let field = wave_ratio(tick, 128, 31);
let dream = wave_ratio(tick, 88, 57);
NebulaGauge::new(crown)
.tick(tick)
.label(format!("thermal crown {:>3}%", (crown * 100.0) as u16))
.palette(forge)
.block(color_block("crown", forge.pulse))
.render(buffer, lower[0]);
NebulaGauge::new(field)
.tick(tick + 11)
.label(format!("magnet lock {:>3}%", (field * 100.0) as u16))
.palette(frost)
.block(color_block("field", frost.pulse))
.render(buffer, lower[1]);
NebulaGauge::new(dream)
.tick(tick + 23)
.label(format!("coolant {:>3}%", (dream * 100.0) as u16))
.palette(AislingPalette::cypherpunk())
.block(color_block("coolant", AislingPalette::cypherpunk().mid))
.render(buffer, lower[2]);
})?;
if event::poll(Duration::from_millis(40))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press
&& matches!(key.code, KeyCode::Char('q') | KeyCode::Esc)
{
break;
}
}
}
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
}
fn color_block(title: &str, color: Color) -> Block<'_> {
Block::new(title)
.with_borders(BorderStyle::Plain)
.with_border_color(color)
.with_inner_margin(scrin::Rect::ZERO)
}