use revue::prelude::App;
use revue::{
event::Key,
style::Color,
widget::{hstack, vstack, Border, Input, List, Progress, RenderContext, Text, View},
};
struct AppState {
items: Vec<String>,
selected: usize,
counter: i32,
input: Input,
progress: f32,
}
impl Default for AppState {
fn default() -> Self {
Self {
items: vec![
"Item 1 - Welcome to Revue!".to_string(),
"Item 2 - A Vue-style TUI".to_string(),
"Item 3 - With CSS styling".to_string(),
"Item 4 - And reactive state".to_string(),
"Item 5 - Built in Rust".to_string(),
],
selected: 0,
counter: 0,
input: Input::new().placeholder("Type here..."),
progress: 0.3,
}
}
}
struct MainView {
state: AppState,
}
impl MainView {
fn new() -> Self {
Self {
state: AppState::default(),
}
}
fn handle_key(&mut self, key: &Key) -> bool {
match key {
Key::Char('k') | Key::Up => {
if self.state.selected > 0 {
self.state.selected -= 1;
}
true
}
Key::Char('j') | Key::Down => {
if self.state.selected < self.state.items.len() - 1 {
self.state.selected += 1;
}
true
}
Key::Char('g') => {
self.state.selected = 0;
true
}
Key::Char('G') => {
self.state.selected = self.state.items.len().saturating_sub(1);
true
}
Key::Char('+') | Key::Char('=') => {
self.state.counter += 1;
self.state.progress = (self.state.progress + 0.1).min(1.0);
true
}
Key::Char('-') => {
self.state.counter -= 1;
self.state.progress = (self.state.progress - 0.1).max(0.0);
true
}
_ => self.state.input.handle_key(key),
}
}
}
impl View for MainView {
fn render(&self, ctx: &mut RenderContext) {
let title = Border::rounded()
.title("Revue")
.fg(Color::CYAN)
.child(Text::new("TUI Framework Demo").fg(Color::WHITE).bold());
let instructions = Text::new("j/k: Nav | g/G: Top/Bot | +/-: Counter/Progress | q: Quit")
.fg(Color::rgb(128, 128, 128));
let counter_text = format!("Counter: {}", self.state.counter);
let counter = Text::new(counter_text).fg(Color::YELLOW);
let progress_bar = Border::single().title("Progress").child(
Progress::new(self.state.progress)
.filled_color(Color::GREEN)
.show_percentage(true),
);
let input_box = Border::single()
.title("Input")
.child(self.state.input.clone());
let list = Border::double().title("Items").fg(Color::BLUE).child(
List::new(self.state.items.clone())
.selected(self.state.selected)
.highlight_bg(Color::BLUE)
.highlight_fg(Color::WHITE),
);
let layout = vstack()
.gap(1)
.child(title)
.child(instructions)
.child(hstack().gap(1).child(counter).child(progress_bar))
.child(input_box)
.child(list);
layout.render(ctx);
}
}
fn main() -> revue::Result<()> {
let mut app = App::builder().build();
let view = MainView::new();
app.run_with_handler(view, |key_event, view: &mut MainView| {
view.handle_key(&key_event.key)
})
}