use std::{io, time::Duration};
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
Terminal,
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
widgets::{Block, Paragraph, Wrap},
};
use scrin_widgets::{AislingExt, AislingPalette, GlyphRain, NebulaGauge, SignalPanel};
type Tui = Terminal<CrosstermBackend<io::Stdout>>;
fn main() -> io::Result<()> {
let mut terminal = setup_terminal()?;
let result = run(&mut terminal);
restore_terminal(terminal)?;
result
}
fn setup_terminal() -> io::Result<Tui> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
Terminal::new(CrosstermBackend::new(stdout))
}
fn restore_terminal(mut terminal: Tui) -> io::Result<()> {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()
}
fn run(terminal: &mut Tui) -> io::Result<()> {
let mut tick = 0_u64;
loop {
terminal.draw(|frame| {
let root = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(6),
Constraint::Min(10),
Constraint::Length(7),
])
.split(frame.area());
frame.render_widget(
Paragraph::new("Scrin Widgets ships ambient fields, luminous progress, signal panels, and Aisling effects that decorate any Ratatui widget. Press q or Esc to leave the dream.")
.block(Block::bordered().title("scrin-widgets"))
.wrap(Wrap { trim: true })
.aisling()
.tick(tick)
.palette(AislingPalette::dream())
.intensity(6),
root[0],
);
let middle = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(58), Constraint::Percentage(42)])
.split(root[1]);
frame.render_widget(
GlyphRain::new(tick)
.density(42)
.palette(AislingPalette::phosphor())
.block(Block::bordered().title("ambient glyph stream")),
middle[0],
);
frame.render_widget(
SignalPanel::new("aisling relay")
.line("phase: lucid")
.line("carrier: 8.13 THz")
.line("noise: below horizon")
.line("mode: exotic TUI")
.tick(tick)
.palette(AislingPalette::flare()),
middle[1],
);
let lower = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(root[2]);
let wave = ((tick % 100) as f64 / 100.0 - 0.5).abs();
let ratio = 1.0 - wave * 2.0;
frame.render_widget(
NebulaGauge::new(ratio)
.tick(tick)
.label(format!("dream charge {:>3}%", (ratio * 100.0) as u16))
.block(Block::bordered().title("nebula gauge")),
lower[0],
);
frame.render_widget(
Paragraph::new("The Aisling wrapper is intentionally generic: render your own widget first, then let scrin-widgets tint the buffer.")
.block(Block::bordered().title("wrapper"))
.wrap(Wrap { trim: true })
.aisling()
.tick(tick + 19)
.palette(AislingPalette::flare())
.intensity(5),
lower[1],
);
})?;
if event::poll(Duration::from_millis(33))? {
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(())
}