use std::io;
use std::time::Duration;
use crossterm::event::{self, Event as CtEvent, KeyCode, KeyEventKind};
use ratatui::style::Modifier;
use ratatui::text::{Line, Span};
use ratatui::{Terminal, TerminalOptions, Viewport};
use tuika::prelude::*;
use tuika::term::hyperlink::HyperlinkBackend;
fn build(frame: u64, theme: &Theme) -> tuika::Element {
let labeled = |style: SpinnerStyle, label: &str| -> tuika::Element {
view! {
row(gap = 1) {
fixed(1) { node(Spinner::new(frame).style(style)) }
text(label.to_string())
}
}
};
let animated = tuika::anim::ping_pong(frame, 120);
view! {
col(padding = tuika::Padding::all(1), gap = 1,
background = ratatui::style::Style::default().bg(theme.background)) {
fixed(5) {
boxed(title = Line::from(Span::styled(" spinners ", theme.accent_style()))) {
col {
fixed(1) { node(labeled(SpinnerStyle::Braille, "Braille")) }
fixed(1) { node(labeled(SpinnerStyle::Line, "Line")) }
fixed(1) { node(labeled(SpinnerStyle::Dots, "Dots")) }
}
}
}
fixed(6) {
boxed(title = Line::from(Span::styled(" progress ", theme.accent_style()))) {
col {
fixed(1) { node(ProgressBar::determinate(0.25).percent(true)) }
fixed(1) { node(ProgressBar::determinate(0.60).percent(true)) }
fixed(1) { node(ProgressBar::determinate(animated).percent(true)) }
fixed(1) { node(ProgressBar::indeterminate(frame)) }
}
}
}
fixed(3) {
boxed(title = Line::from(Span::styled(" loader ", theme.accent_style())),
title_bottom = Line::from(Span::styled(" 3 of 3 ", theme.muted_style()))) {
node(Loader::new(frame, "working…").hint("esc to quit"))
}
}
fixed(5) {
boxed(title = Line::from(Span::styled(" alignment ", theme.accent_style()))) {
node(Text::new(vec![
Line::from(Span::styled("left", theme.muted_style())),
Line::from(Span::styled("centered", theme.accent_style())).centered(),
Line::from(Span::styled("right", theme.muted_style())).right_aligned(),
]))
}
}
fixed(3) {
row(gap = 1) {
grow(1) {
node(FocusScope::focused(view! {
boxed(title = " focused ") { text("theme.border_focused") }
}))
}
grow(1) {
node(FocusScope::unfocused(view! {
boxed(title = " idle ") { text("theme.border") }
}))
}
grow(1) {
boxed(title = Line::from(Span::styled(" danger ", theme.accent_style())),
border_color = ratatui::style::Color::Red) {
text("explicit color")
}
}
}
}
fixed(5) {
boxed(title = Line::from(Span::styled(" table ", theme.accent_style()))) {
node({
let mut sel = SelectState::new();
sel.select(1);
let columns = vec![
Column::fixed(Span::styled("branch", theme.muted_style()), 8),
Column::auto(Span::styled("ahead", theme.muted_style())),
Column::flex(Span::styled("subject", theme.muted_style()), 1),
];
let rows = vec![
vec![Line::from("main"), Line::from("0"), Line::from("release cut")],
vec![Line::from("wip"), Line::from("3"), Line::from("table component")],
vec![Line::from("fix"), Line::from("1"), Line::from("scrollbar math")],
];
Table::new(columns, rows, &sel)
.header(true)
.caret('▶')
.preserve_selection_fg(true)
})
}
}
grow(1) { spacer() }
fixed(1) {
node(Text::new(vec![Line::from(vec![
Span::styled("docs ", theme.muted_style()),
Span::styled(
"https://github.com/everruns/tuika",
theme.accent_style().add_modifier(Modifier::UNDERLINED),
),
Span::styled(" · press q to quit", theme.muted_style()),
])]))
}
}
}
}
fn main() -> io::Result<()> {
let _session = tuika::TerminalSession::enter()?;
let mut terminal = Terminal::with_options(
HyperlinkBackend::new(io::stdout(), true),
TerminalOptions {
viewport: Viewport::Fullscreen,
},
)?;
let theme = Theme::default();
let mut progress = tuika::term::progress::TerminalProgress::new();
progress.indeterminate();
let mut frame = 0u64;
loop {
terminal.draw(|f| {
let area = f.area();
let root = build(frame, &theme);
tuika::paint(f.buffer_mut(), area, &theme, root.as_ref(), &[]);
})?;
if event::poll(Duration::from_millis(80))?
&& let CtEvent::Key(key) = event::read()?
&& key.kind != KeyEventKind::Release
&& matches!(key.code, KeyCode::Char('q') | KeyCode::Esc)
{
break;
}
frame = frame.wrapping_add(1);
}
progress.clear();
let _ = terminal.clear();
drop(terminal);
Ok(())
}