use crossterm::{
cursor,
event::{self, Event, KeyCode, KeyModifiers},
execute,
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
};
use scrin::core::buffer::Buffer;
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::layout::{Constraint, Layout};
use scrin::status_bar::{StatusBar, StatusBarPosition};
use scrin::widgets::barchart::{Bar, BarChart};
use scrin::widgets::block::{Block, BorderStyle};
use scrin::widgets::gauge::Gauge;
use scrin::widgets::list::{List, ListItem};
use scrin::widgets::scrollbar::{ScrollBar, ScrollBarOrientation};
use scrin::widgets::sparkline::Sparkline;
use scrin::widgets::table::{self, Table};
use scrin::widgets::tabs::Tabs;
use scrin::widgets::Widget;
use std::io::{self, Write};
fn main() -> io::Result<()> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(stdout, EnterAlternateScreen, cursor::Hide)?;
let tab_names = &["Gauges", "Lists", "Tables", "Charts", "Scroll"];
let mut tab_index: usize = 0;
let mut progress: f32 = 0.0;
let mut progress_dir: f32 = 0.008;
let mut scroll_pos: usize = 0;
let mut list_selected: usize = 0;
let mut status = StatusBar::new().with_position(StatusBarPosition::Bottom);
let result = (|| -> io::Result<()> {
loop {
let (cols, rows) = terminal::size()?;
let mut buffer = Buffer::new(cols as usize, rows as usize);
let outer = Rect::new(0, 0, cols, rows - 2);
let block = Block::new("Widget Showcase — Tab switch, q quit")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(88, 166, 255));
block.render(&mut buffer, outer);
let inner = block.inner(outer);
let main_layout = Layout::vertical(vec![Constraint::Length(1), Constraint::Min(0)]);
let main_rects = main_layout.split(inner);
let tabs = Tabs::new(tab_names).with_selected(tab_index);
tabs.render(&mut buffer, main_rects[0]);
match tab_index {
0 => render_gauges(&mut buffer, main_rects[1], progress),
1 => render_lists(&mut buffer, main_rects[1], list_selected),
2 => render_tables(&mut buffer, main_rects[1]),
3 => render_charts(&mut buffer, main_rects[1], progress),
4 => render_scroll(&mut buffer, main_rects[1], scroll_pos),
_ => {}
}
progress += progress_dir;
if progress >= 1.0 {
progress = 1.0;
progress_dir = -0.005;
} else if progress <= 0.0 {
progress = 0.0;
progress_dir = 0.008;
}
status.set_left(
&format!("Tab: {}", tab_names[tab_index]),
Color::rgb(88, 166, 255),
);
status.set_right(
&format!("Progress: {:.0}%", progress * 100.0),
Color::rgb(139, 148, 158),
);
let status_area = Rect::new(0, rows - 1, cols, 1);
status.render(&mut buffer, status_area);
write!(stdout, "\x1b[H{}", buffer.to_ansi_string())?;
stdout.flush()?;
if event::poll(std::time::Duration::from_millis(80))? {
if let Event::Key(key) = event::read()? {
match (key.modifiers, key.code) {
(KeyModifiers::CONTROL, KeyCode::Char('c'))
| (KeyModifiers::CONTROL, KeyCode::Char('q'))
| (_, KeyCode::Char('q')) => return Ok(()),
(_, KeyCode::Tab) | (_, KeyCode::Right) => {
tab_index = (tab_index + 1) % tab_names.len();
}
(_, KeyCode::BackTab) | (_, KeyCode::Left) => {
tab_index = if tab_index == 0 {
tab_names.len() - 1
} else {
tab_index - 1
};
}
(_, KeyCode::Up) | (_, KeyCode::Char('k')) => {
list_selected = list_selected.saturating_sub(1);
scroll_pos = scroll_pos.saturating_sub(1);
}
(_, KeyCode::Down) | (_, KeyCode::Char('j')) => {
list_selected += 1;
scroll_pos += 1;
}
_ => {}
}
}
}
}
})();
execute!(stdout, cursor::Show, LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
result
}
fn render_gauges(buf: &mut Buffer, area: Rect, progress: f32) {
let layout = Layout::vertical(vec![
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(6),
Constraint::Min(0),
]);
let rects = layout.split(area);
let gauge1 = Gauge::new()
.with_ratio(progress as f64)
.with_label("Download");
gauge1.render(buf, rects[0]);
let gauge2 = Gauge::new()
.with_ratio((1.0 - progress) as f64)
.with_label("Upload");
gauge2.render(buf, rects[1]);
let gauge3 = Gauge::new()
.with_ratio((progress * 0.7 + 0.15) as f64)
.with_label("CPU");
gauge3.render(buf, rects[2]);
let block = Block::new("Info")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(48, 54, 61));
block.render(buf, rects[3]);
let inner = block.inner(rects[3]);
let lines = [
"Gauges show progress with animated bars.",
"They support unicode and ASCII modes.",
"Use with_label() to add text overlay.",
];
for (i, line) in lines.iter().enumerate() {
if i as u16 >= inner.height {
break;
}
buf.set_str(
inner.x as usize,
inner.y as usize + i,
line,
Color::rgb(110, 118, 129),
None,
);
}
let sparkline = Sparkline::new()
.with_data(vec![
3, 5, 7, 4, 6, 8, 5, 7, 9, 6, 8, 10, 7, 9, 11, 8, 12, 10, 8, 6,
])
.with_max(12)
.with_color(Color::rgb(88, 166, 255));
sparkline.render(buf, rects[4]);
}
fn render_lists(buf: &mut Buffer, area: Rect, selected: usize) {
let block = Block::new("List Widget")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(88, 166, 255));
block.render(buf, area);
let inner = block.inner(area);
let items = vec![
ListItem::new("Dashboard"),
ListItem::new("Settings"),
ListItem::new("Analytics"),
ListItem::new("Notifications"),
ListItem::new("Profile"),
ListItem::new("Help"),
ListItem::new("About"),
ListItem::new("Logout"),
];
let sel = selected % items.len();
let list = List::new(&items).with_selected(sel);
list.render(buf, inner);
}
fn render_tables(buf: &mut Buffer, area: Rect) {
let block = Block::new("Table Widget")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(88, 166, 255));
block.render(buf, area);
let inner = block.inner(area);
let header = table::Row::new(vec![
table::Cell::new("Name"),
table::Cell::new("Status"),
table::Cell::new("Progress"),
]);
let rows = vec![
table::Row::new(vec![
table::Cell::new("Build"),
table::Cell::new("Running"),
table::Cell::new("42%"),
]),
table::Row::new(vec![
table::Cell::new("Test"),
table::Cell::new("Passed"),
table::Cell::new("100%"),
]),
table::Row::new(vec![
table::Cell::new("Deploy"),
table::Cell::new("Pending"),
table::Cell::new("0%"),
]),
table::Row::new(vec![
table::Cell::new("Lint"),
table::Cell::new("Failed"),
table::Cell::new("67%"),
]),
];
let widths = &[12, 12, 10];
let table = Table::new(&rows, widths).with_header(&header);
table.render(buf, inner);
}
fn render_charts(buf: &mut Buffer, area: Rect, progress: f32) {
let layout = Layout::vertical(vec![Constraint::Length(10), Constraint::Min(0)]);
let rects = layout.split(area);
let bar_chart = BarChart::new().with_bars(vec![
Bar::new("CPU", ((progress * 80.0) as u64).max(5)),
Bar::new("MEM", (progress * 60.0 + 20.0) as u64),
Bar::new("DISK", 45),
Bar::new("NET", (progress * 50.0 + 10.0) as u64),
Bar::new("GPU", (progress * 70.0 + 5.0) as u64),
]);
bar_chart.render(buf, rects[0]);
let block = Block::new("Chart Info")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(48, 54, 61));
block.render(buf, rects[1]);
let inner = block.inner(rects[1]);
let lines = [
"BarChart: vertical or horizontal bars",
"Chart: scatter plots with datasets",
"Sparkline: real-time data visualization",
];
for (i, line) in lines.iter().enumerate() {
if i as u16 >= inner.height {
break;
}
buf.set_str(
inner.x as usize,
inner.y as usize + i,
line,
Color::rgb(110, 118, 129),
None,
);
}
}
fn render_scroll(buf: &mut Buffer, area: Rect, scroll_pos: usize) {
let block = Block::new("Scrollbar Demo — up/down to scroll")
.with_borders(BorderStyle::Rounded)
.with_border_color(Color::rgb(88, 166, 255));
block.render(buf, area);
let inner = block.inner(area);
let line_layout = Layout::horizontal(vec![Constraint::Min(0), Constraint::Length(2)]);
let line_rects = line_layout.split(inner);
let total_lines: usize = 50;
let viewport = line_rects[0].height as usize;
let max_scroll = total_lines.saturating_sub(viewport);
let pos = scroll_pos.min(max_scroll);
for i in 0..viewport.min(total_lines) {
let line_num = pos + i;
if line_num >= total_lines {
break;
}
let text = format!(" Line {:03}: Lorem ipsum dolor sit amet", line_num + 1);
let display: String = text.chars().take(line_rects[0].width as usize).collect();
let color = if line_num % 5 == 0 {
Color::rgb(88, 166, 255)
} else {
Color::rgb(139, 148, 158)
};
buf.set_str(
line_rects[0].x as usize,
line_rects[0].y as usize + i,
&display,
color,
None,
);
}
let scrollbar = ScrollBar::new(ScrollBarOrientation::Vertical)
.with_position(pos)
.with_total(total_lines)
.with_viewport(viewport);
scrollbar.render(buf, line_rects[1]);
}