use envision::prelude::*;
use ratatui::layout::{Alignment, Constraint, Layout};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
struct CounterApp;
#[derive(Default, Clone)]
struct State {
count: i32,
history: Vec<String>,
}
#[derive(Clone, Debug)]
enum Msg {
Increment,
Decrement,
Reset,
Quit,
}
impl App for CounterApp {
type State = State;
type Message = Msg;
fn init() -> (State, Command<Msg>) {
let mut state = State::default();
state.history.push("App initialized".to_string());
(state, Command::none())
}
fn update(state: &mut State, msg: Msg) -> Command<Msg> {
match msg {
Msg::Increment => {
state.count += 1;
state
.history
.push(format!("Incremented to {}", state.count));
}
Msg::Decrement => {
state.count -= 1;
state
.history
.push(format!("Decremented to {}", state.count));
}
Msg::Reset => {
state.count = 0;
state.history.push("Reset to 0".to_string());
}
Msg::Quit => {
return Command::quit();
}
}
if state.history.len() > 5 {
state.history.remove(0);
}
Command::none()
}
fn view(state: &State, frame: &mut Frame) {
let area = frame.area();
let chunks = Layout::vertical([
Constraint::Length(3), Constraint::Length(5), Constraint::Length(3), Constraint::Min(0), ])
.split(area);
let title = Paragraph::new("Counter App - TEA Architecture Demo")
.style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL));
frame.render_widget(title, chunks[0]);
let counter_style = if state.count > 0 {
Style::default().fg(Color::Green)
} else if state.count < 0 {
Style::default().fg(Color::Red)
} else {
Style::default().fg(Color::Yellow)
};
let counter = Paragraph::new(format!("{}", state.count))
.style(counter_style.add_modifier(Modifier::BOLD))
.alignment(Alignment::Center)
.block(
Block::default()
.borders(Borders::ALL)
.title("Count")
.title_alignment(Alignment::Center),
);
frame.render_widget(counter, chunks[1]);
let controls = Paragraph::new(Line::from(vec![
Span::styled("[+] ", Style::default().fg(Color::Green)),
Span::raw("Increment "),
Span::styled("[-] ", Style::default().fg(Color::Red)),
Span::raw("Decrement "),
Span::styled("[R] ", Style::default().fg(Color::Yellow)),
Span::raw("Reset "),
Span::styled("[Q] ", Style::default().fg(Color::Magenta)),
Span::raw("Quit"),
]))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).title("Controls"));
frame.render_widget(controls, chunks[2]);
let history_lines: Vec<Line> = state
.history
.iter()
.map(|s| Line::from(format!(" {}", s)))
.collect();
let history = Paragraph::new(history_lines)
.block(Block::default().borders(Borders::ALL).title("History"));
frame.render_widget(history, chunks[3]);
}
fn handle_event(event: &Event) -> Option<Msg> {
use envision::input::Key;
if let Some(key) = event.as_key() {
match key.code {
Key::Char('+') | Key::Up => Some(Msg::Increment),
Key::Char('-') | Key::Down => Some(Msg::Decrement),
Key::Char('r') => Some(Msg::Reset),
Key::Char('q') | Key::Esc => Some(Msg::Quit),
_ => None,
}
} else {
None
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut vt = Runtime::<CounterApp, _>::virtual_builder(60, 20).build()?;
println!("=== Counter App Demo ===\n");
vt.tick()?;
println!("Initial state:");
println!("{}\n", vt.display());
vt.dispatch(Msg::Increment);
vt.dispatch(Msg::Increment);
vt.dispatch(Msg::Increment);
vt.tick()?;
println!("After 3 increments:");
println!("{}\n", vt.display());
vt.dispatch(Msg::Decrement);
vt.tick()?;
println!("After 1 decrement:");
println!("{}\n", vt.display());
vt.dispatch(Msg::Reset);
vt.tick()?;
println!("After reset:");
println!("{}\n", vt.display());
println!("Final count: {}", vt.state().count);
println!("History entries: {}", vt.state().history.len());
Ok(())
}