Skip to content

Commit

Permalink
feat(examples): add demo2 example
Browse files Browse the repository at this point in the history
This is example is aimed at creating a github social preview image and
eventually replacing the current main demo.

![Made with VHS](https://vhs.charm.sh/vhs-77i6trU7Mw0JeO4CQ7HLIM.gif)
  • Loading branch information
joshka committed Sep 14, 2023
1 parent d4976d4 commit 4c8395e
Show file tree
Hide file tree
Showing 15 changed files with 979 additions and 39 deletions.
13 changes: 10 additions & 3 deletions Cargo.toml
Expand Up @@ -47,14 +47,16 @@ document-features = { version = "0.2.7", optional = true }

[dev-dependencies]
anyhow = "1.0.71"
argh = "0.1"
argh = "0.1.12"
better-panic = "0.3.0"
cargo-husky = { version = "1.5.0", default-features = false, features = [
"user-hooks",
] }
criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.5.1", features = ["html_reports"] }
fakeit = "1.1"
rand = "0.8"
lipsum = "0.9.0"
rand = "0.8.5"
palette = "0.7.3"
pretty_assertions = "1.4.0"

[features]
Expand Down Expand Up @@ -148,6 +150,11 @@ name = "demo"
# this runs for all of the terminal backends, so it can't be built using --all-features or scraped
doc-scrape-examples = false

[[example]]
name = "demo2"
# this runs for all of the terminal backends, so it can't be built using --all-features or scraped
doc-scrape-examples = false

[[example]]
name = "gauge"
required-features = ["crossterm"]
Expand Down
59 changes: 23 additions & 36 deletions examples/colors_rgb.rs
Expand Up @@ -13,7 +13,10 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use itertools::Itertools;
use palette::{
convert::{FromColorUnclamped, IntoColorUnclamped},
Okhsv, Srgb,
};
use ratatui::{prelude::*, widgets::*};

type Result<T> = std::result::Result<T, Box<dyn Error>>;
Expand Down Expand Up @@ -77,9 +80,8 @@ struct RgbColors;
impl Widget for RgbColors {
fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Self::layout(area);
let rgb_colors = Self::create_rgb_color_grid(area.width, area.height * 2);
Self::render_title(layout[0], buf);
Self::render_colors(layout[1], buf, rgb_colors);
Self::render_colors(layout[1], buf);
}
}

Expand All @@ -99,41 +101,26 @@ impl RgbColors {
}

/// Render a colored grid of half block characters (`"▀"`) each with a different RGB color.
fn render_colors(area: Rect, buf: &mut Buffer, rgb_colors: Vec<Vec<Color>>) {
for (x, column) in (area.left()..area.right()).zip(rgb_colors.iter()) {
for (y, (fg, bg)) in (area.top()..area.bottom()).zip(column.iter().tuples()) {
let cell = buf.get_mut(x, y);
cell.fg = *fg;
cell.bg = *bg;
cell.symbol = "▀".into();
}
}
}

/// Generate a smooth grid of colors
///
/// Red ranges from 0 to 255 across the x axis. Green ranges from 0 to 255 across the y axis.
/// Blue repeats every 32 pixels in both directions, but flipped every 16 pixels so that it
/// doesn't transition sharply from light to dark.
///
/// The result stored in a 2d vector of colors with the x axis as the first dimension, and the
/// y axis the second dimension.
fn create_rgb_color_grid(width: u16, height: u16) -> Vec<Vec<Color>> {
let mut result = vec![];
for x in 0..width {
let mut column = vec![];
for y in 0..height {
// flip both axes every 16 pixels. E.g. [0, 1, ... 15, 15, ... 1, 0]
let yy = if (y % 32) < 16 { y % 32 } else { 31 - y % 32 };
let xx = if (x % 32) < 16 { x % 32 } else { 31 - x % 32 };
let r = (256 * x / width) as u8;
let g = (256 * y / height) as u8;
let b = (yy * 16 + xx) as u8;
column.push(Color::Rgb(r, g, b))
fn render_colors(area: Rect, buf: &mut Buffer) {
for (xi, x) in (area.left()..area.right()).enumerate() {
for (yi, y) in (area.top()..area.bottom()).enumerate() {
let hue = xi as f32 * 360.0 / area.width as f32;

let value_fg = (yi as f32) / (area.height as f32 - 0.5);
let fg = Okhsv::<f32>::new(hue, Okhsv::max_saturation(), value_fg);
let fg: Srgb = fg.into_color_unclamped();
let fg: Srgb<u8> = fg.into_format();
let fg = Color::Rgb(fg.red, fg.green, fg.blue);

let value_bg = (yi as f32 + 0.5) / (area.height as f32 - 0.5);
let bg = Okhsv::new(hue, Okhsv::max_saturation(), value_bg);
let bg = Srgb::<f32>::from_color_unclamped(bg);
let bg: Srgb<u8> = bg.into_format();
let bg = Color::Rgb(bg.red, bg.green, bg.blue);

buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
}
result.push(column);
}
result
}
}

Expand Down
34 changes: 34 additions & 0 deletions examples/demo2.tape
@@ -0,0 +1,34 @@
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
# To run this script, install vhs and run `vhs ./examples/demo.tape`
Output "target/demo2.gif"
Set Theme "OceanicMaterial"
# Github social preview size (1280x640 with 80px padding)
Set Width 1280
Set Height 640
Set Padding 80
Hide
Type "cargo run --example demo2"
Enter
Sleep 2s
Set TypingSpeed 200ms
Show
Sleep 1.5s
Down 10
Sleep 1s
Right
Sleep 1.5s
Down 20
Right
Sleep 1.5s
Down@50ms 40
Sleep 1s
Right
Sleep 1.5s
Right
Sleep 1.5s
Down @2s 4
Sleep 2s
Right
Sleep 1.5s
Down@0.5s 28
Sleep 5s
131 changes: 131 additions & 0 deletions examples/demo2/app.rs
@@ -0,0 +1,131 @@
use std::{
io::{self, stdout, Stdout},
time::Duration,
};

use crossterm::{
event::{self, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use ratatui::prelude::*;

use crate::{main_view::MainView, *};

pub struct App {
terminal: Terminal<CrosstermBackend<Stdout>>,
should_quit: bool,
selected_tab: usize,
selected_row: usize,
}

impl App {
pub fn new() -> Result<Self> {
// this size is to match the size of the terminal when running the demo
// using vhs in a 1280x640 sized window (github social preview size)
let options = TerminalOptions {
viewport: Viewport::Fixed(Rect::new(0, 0, 81, 18)),
// viewport: Viewport::Fullscreen,
};
let terminal = Terminal::with_options(CrosstermBackend::new(io::stdout()), options)?;
Ok(Self {
terminal,
should_quit: false,
selected_tab: 0,
selected_row: 0,
})
}

pub fn run(&mut self) -> Result<()> {
setup_terminal()?;
while !self.should_quit {
self.draw()?;
self.handle_events()?;
}
restore_terminal()?;
Ok(())
}

fn draw(&mut self) -> Result<()> {
self.terminal
.draw(|frame| {
let area = frame.size();
let widget = MainView {
selected_tab: self.selected_tab,
selected_row: self.selected_row,
};
frame.render_widget(widget, area);
})
.context("terminal.draw")?;
Ok(())
}

fn handle_events(&mut self) -> Result<()> {
if !event::poll(Duration::from_millis(16))? {
return Ok(());
}
match event::read()? {
event::Event::Key(key) => self.handle_key_event(key),
_ => Ok(()),
}
}

fn handle_key_event(&mut self, key: event::KeyEvent) -> std::result::Result<(), anyhow::Error> {
if key.kind != KeyEventKind::Press {
return Ok(());
}
match key.code {
event::KeyCode::Char('q') => {
self.should_quit = true;
}
event::KeyCode::Left | event::KeyCode::Char('h') => {
self.selected_tab = self.selected_tab.saturating_sub(1);
self.selected_row = 0;
}
event::KeyCode::Right | event::KeyCode::Char('l') => {
self.selected_tab = self.selected_tab.saturating_add(1).min(5);
self.selected_row = 0;
}
event::KeyCode::Up | event::KeyCode::Char('k') => {
self.selected_row = self.selected_row.saturating_sub(1);
}
event::KeyCode::Down | event::KeyCode::Char('j') => {
self.selected_row = self.selected_row.saturating_add(1);
}
_ => {}
};
Ok(())
}
}

impl Drop for App {
fn drop(&mut self) {
let _ = restore_terminal();
}
}

fn setup_terminal() -> Result<()> {
enable_raw_mode().context("enable raw mode")?;
stdout()
.execute(EnterAlternateScreen)
.context("enter alternate screen")?;
Ok(())
}

fn restore_terminal() -> Result<()> {
disable_raw_mode().context("disable raw mode")?;
stdout()
.execute(LeaveAlternateScreen)
.context("leave alternate screen")?;
Ok(())
}

pub fn install_panic_hook() {
better_panic::install();
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
hook(info);
std::process::exit(1);
}));
}
86 changes: 86 additions & 0 deletions examples/demo2/bars.rs
@@ -0,0 +1,86 @@
use ratatui::{prelude::*, widgets::*};

pub fn render(area: Rect, buf: &mut Buffer) {
let area = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
.split(area);

render_simple_barchart(area[0], buf);
render_horizontal_barchart(area[1], buf);
}

fn render_simple_barchart(area: Rect, buf: &mut Buffer) {
let data = vec![
("Jan", 10),
("Feb", 20),
("Mar", 30),
("Apr", 40),
("May", 50),
("Jun", 60),
("Jul", 70),
];
let block = Block::default()
.title("BarChart")
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
BarChart::default()
.data(&data)
.block(block)
.bar_width(3)
.bar_gap(1)
.value_style(
Style::default()
.fg(Color::Black)
.bg(Color::Green)
.add_modifier(Modifier::ITALIC),
)
.label_style(Style::default().fg(Color::Yellow))
.bar_style(Style::default().fg(Color::Green))
.render(area, buf);
}

fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
// https://www.videocardbenchmark.net/high_end_gpus.html
let nvidia = Style::new().bg(Color::Green);
let amd = Style::new().bg(Color::Red);
let data = [
Bar::default()
.text_value("GeForce RTX 4090 (38,978)".into())
.value_style(nvidia)
.value(38978),
Bar::default()
.text_value("GeForce RTX 4080 (34,879)".into())
.value_style(nvidia)
.value(34879),
Bar::default()
.text_value("Radeon PRO W7800 (32,146)".into())
.value_style(amd)
.value(32146),
Bar::default()
.text_value("GeForce RTX 4070 Ti (31,659)".into())
.value_style(nvidia)
.value(31659),
Bar::default()
.text_value("Radeon RX 7900 XTX (31,180)".into())
.value_style(amd)
.value(31180),
];
let group = BarGroup::default().label("GPU".into()).bars(&data);
let block = Block::default()
.title("Passmark")
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
BarChart::default()
.direction(Direction::Horizontal)
.block(block)
.data(group)
.value_style(
Style::default()
.fg(Color::Black)
.add_modifier(Modifier::ITALIC),
)
.label_style(Style::default().fg(Color::Yellow))
.bar_style(Style::default().light_blue())
.render(area, buf);
}

0 comments on commit 4c8395e

Please sign in to comment.