use std::error::Error;
use std::time::Duration;
use tui_realm_stdlib::components::{Gauge, Label};
use tuirealm::application::PollStrategy;
use tuirealm::command::CmdResult;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent};
use tuirealm::listener::{Poll, PortResult, SyncPort};
use tuirealm::props::{
AttrValue, AttrValueRef, Attribute, BorderType, Borders, Color, HorizontalAlignment,
PropPayload, PropPayloadRef, PropValue, PropValueRef, QueryResult, Style, Title,
};
use tuirealm::ratatui::layout::{Constraint, Direction as LayoutDirection, Layout};
use tuirealm::terminal::TerminalAdapter;
mod utils;
use utils::{Loader, Model};
#[derive(Debug, PartialEq)]
pub enum Msg {
AppClose,
GaugeAlfaBlur,
GaugeBetaBlur,
Redraw,
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Id {
Label,
GaugeAlfa,
GaugeBeta,
}
#[derive(PartialEq, Clone, PartialOrd)]
enum UserEvent {
Loaded(f64),
}
impl Eq for UserEvent {}
impl Model<Id, Msg, UserEvent> {
fn view(&mut self) {
self.terminal
.draw(|f| {
let chunks = Layout::default()
.direction(LayoutDirection::Vertical)
.margin(1)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(1),
]
.as_ref(),
)
.split(f.area());
self.app.view(&Id::Label, f, chunks[0]);
self.app.view(&Id::GaugeAlfa, f, chunks[1]);
self.app.view(&Id::GaugeBeta, f, chunks[2]);
})
.expect("Drawing to the terminal failed");
}
fn update(&mut self, msg: Msg) {
self.redraw = true;
match msg {
Msg::AppClose => {
self.quit = true;
}
Msg::GaugeAlfaBlur => {
assert!(self.app.active(&Id::GaugeBeta).is_ok());
}
Msg::GaugeBetaBlur => {
assert!(self.app.active(&Id::GaugeAlfa).is_ok());
}
Msg::Redraw => (),
}
}
fn mount_main(&mut self) -> Result<(), Box<dyn Error>> {
self.app
.mount(Id::Label, Box::new(KeyboardLabel::default()), vec![])?;
self.app
.mount(Id::GaugeAlfa, Box::new(GaugeAlfa::default()), vec![])?;
self.app
.mount(Id::GaugeBeta, Box::new(GaugeBeta::default()), vec![])?;
self.app.active(&Id::GaugeAlfa)?;
Ok(())
}
}
fn main() {
let mut model = Model::new_ports([SyncPort::new(
Box::new(Loader::default()),
Duration::from_millis(50),
1,
)]);
model.mount_main().expect("Mount all main components");
while !model.quit {
if let Ok(messages) = model
.app
.tick(PollStrategy::Once(Duration::from_millis(10)))
{
for msg in messages {
model.update(msg);
}
}
if model.redraw {
model.view();
model.redraw = false;
}
}
}
impl Poll<UserEvent> for Loader {
fn poll(&mut self) -> PortResult<Option<Event<UserEvent>>> {
Ok(Some(Event::User(UserEvent::Loaded(self.load()))))
}
}
#[derive(Component)]
struct KeyboardLabel {
component: Label,
}
impl Default for KeyboardLabel {
fn default() -> Self {
Self {
component: Label::default().text("Press <TAB> to switch between bars; <ESC> to exit"),
}
}
}
impl AppComponent<Msg, UserEvent> for KeyboardLabel {
fn on(&mut self, _: &Event<UserEvent>) -> Option<Msg> {
None
}
}
#[derive(Component)]
struct GaugeAlfa {
component: Gauge,
}
impl Default for GaugeAlfa {
fn default() -> Self {
Self {
component: Gauge::default()
.borders(
Borders::default()
.color(Color::Green)
.modifiers(BorderType::Rounded),
)
.foreground(Color::Green)
.inactive(Style::new().fg(Color::Gray))
.label("0%")
.title(Title::from("Fast Loading...").alignment(HorizontalAlignment::Center))
.progress(0.0),
}
}
}
impl AppComponent<Msg, UserEvent> for GaugeAlfa {
fn on(&mut self, ev: &Event<UserEvent>) -> Option<Msg> {
match ev {
Event::User(UserEvent::Loaded(prog)) => {
let label = format!("{:02}%", (prog * 100.0) as usize);
self.attr(
Attribute::Value,
AttrValue::Payload(PropPayload::Single(PropValue::F64(*prog))),
);
self.attr(Attribute::Text, AttrValue::String(label));
CmdResult::NoChange
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::GaugeAlfaBlur),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose),
_ => CmdResult::NoChange,
};
Some(Msg::Redraw)
}
}
#[derive(Component)]
struct GaugeBeta {
component: Gauge,
}
impl Default for GaugeBeta {
fn default() -> Self {
Self {
component: Gauge::default()
.borders(
Borders::default()
.color(Color::Yellow)
.modifiers(BorderType::Rounded),
)
.foreground(Color::Yellow)
.inactive(Style::new().fg(Color::Gray))
.label("0%")
.title(Title::from("Slow Loading...").alignment(HorizontalAlignment::Center))
.progress(0.0),
}
}
}
impl AppComponent<Msg, UserEvent> for GaugeBeta {
fn on(&mut self, ev: &Event<UserEvent>) -> Option<Msg> {
match ev {
Event::User(UserEvent::Loaded(_)) => {
let mut prog = self
.query(Attribute::Value)
.as_ref()
.map(QueryResult::as_ref)
.and_then(AttrValueRef::as_payload)
.and_then(PropPayloadRef::as_single)
.and_then(PropValueRef::as_f64)
.unwrap_or_default();
prog += 0.001f64;
let label = format!("{:02}%", (prog * 100.0) as usize);
self.attr(
Attribute::Value,
AttrValue::Payload(PropPayload::Single(PropValue::F64(prog))),
);
self.attr(Attribute::Text, AttrValue::String(label));
CmdResult::NoChange
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::GaugeBetaBlur),
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose),
_ => CmdResult::NoChange,
};
Some(Msg::Redraw)
}
}