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
replacing the current demo example.

  ![Made with VHS](https://vhs.charm.sh/vhs-3pibRTVrIP1eoiAgGjdXh5.gif)
  • Loading branch information
joshka committed Sep 15, 2023
1 parent 638d596 commit e4c3732
Show file tree
Hide file tree
Showing 17 changed files with 1,205 additions and 49 deletions.
3 changes: 3 additions & 0 deletions .markdownlint.yaml
@@ -1,10 +1,13 @@
# configuration for https://github.com/DavidAnson/markdownlint

first-line-heading: false
no-inline-html:
allowed_elements:
- img
- details
- summary
- div
- br
line-length:
line_length: 100

Expand Down
14 changes: 11 additions & 3 deletions Cargo.toml
Expand Up @@ -48,14 +48,16 @@ lru = "0.11.1"

[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 @@ -149,6 +151,12 @@ 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"
required-features = ["crossterm"]
# 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
29 changes: 19 additions & 10 deletions README.md
@@ -1,23 +1,32 @@
# Ratatui

<img align="left" src="https://avatars.githubusercontent.com/u/125200832?s=128&v=4">
![Demo of Ratatui](https://repository-images.githubusercontent.com/600886023/96016d23-3bdd-4611-8c03-2e8b5836f900)
<!-- See RELEASE.md for instructions on creating the demo gif --->

`ratatui` is a [Rust](https://www.rust-lang.org) library that is all about cooking up terminal user interfaces.
It is a community fork of the original [tui-rs](https://github.com/fdehau/tui-rs)
project.
<div align="center">

[![Crates.io](https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square)](https://crates.io/crates/ratatui)
[![License](https://img.shields.io/crates/l/ratatui?style=flat-square)](./LICENSE) [![GitHub CI
Status](https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github)](https://github.com/ratatui-org/ratatui/actions?query=workflow%3ACI+)
[![Docs.rs](https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square)](https://docs.rs/crate/ratatui/)
[![Docs.rs](https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square)](https://docs.rs/crate/ratatui/)<br>
[![Dependency
Status](https://deps.rs/repo/github/ratatui-org/ratatui/status.svg?style=flat-square)](https://deps.rs/repo/github/ratatui-org/ratatui)
[![Codecov](https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST)](https://app.codecov.io/gh/ratatui-org/ratatui)
[![Discord](https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square)](https://discord.gg/pMCEU9hNEj)
[![Matrix](https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix)](https://matrix.to/#/#ratatui:matrix.org)
[![Matrix](https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix)](https://matrix.to/#/#ratatui:matrix.org)<br>
[Documentation](https://docs.rs/ratatui)
· [Examples](https://github.com/ratatui-org/ratatui/tree/main/examples)
· [Report a bug](https://github.com/ratatui-org/ratatui/issues/new?labels=bug&projects=&template=bug_report.md)
· [Request a Feature](https://github.com/ratatui-org/ratatui/issues/new?labels=enhancement&projects=&template=feature_request.md)
· [Send a Pull Request](https://github.com/ratatui-org/ratatui/compare)

<!-- See RELEASE.md for instructions on creating the demo gif --->
![Demo of Ratatui](https://vhs.charm.sh/vhs-tF0QbuPbtHgUeG0sTVgFr.gif)
</div>

<img align="left" src="https://avatars.githubusercontent.com/u/125200832?s=128&v=4">

# Ratatui

`ratatui` is a [Rust](https://www.rust-lang.org) library that is all about cooking up terminal user
interfaces. It is a community fork of the original [tui-rs](https://github.com/fdehau/tui-rs)
project.

<details>
<summary>Table of Contents</summary>
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
28 changes: 28 additions & 0 deletions examples/demo2.tape
@@ -0,0 +1,28 @@
# 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
Show
# About screen
Sleep 5s
Tab
# Email
Sleep 5s
Tab
# Trace route
Sleep 5s
Tab
# Misc
Sleep 5s
Tab
# Recipe
Sleep 5s
Tab
127 changes: 127 additions & 0 deletions examples/demo2/app.rs
@@ -0,0 +1,127 @@
use std::{io::Stdout, time::Duration};

use anyhow::{Context, Result};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use itertools::Itertools;
use ratatui::prelude::*;

use crate::{
app_widget::AppWidget,
tabs,
tabs::{AboutTab, EmailTab, MiscWidgetsTab, RecipeTab, Tab, TracerouteTab},
tui,
};

pub struct App {
terminal: Terminal<CrosstermBackend<Stdout>>,
should_quit: bool,
tab_index: usize,
selected_index: usize,
tabs: Vec<Box<dyn Tab>>,
}

impl App {
pub fn new() -> Result<Self> {
let terminal = tui::create_terminal()?;
Ok(Self {
terminal,
should_quit: false,
tab_index: 0,
selected_index: 0,
tabs: vec![
Box::new(tabs::AboutTab::new()),
Box::new(tabs::EmailTab::new(0)),
Box::new(tabs::TracerouteTab::new(0)),
Box::new(tabs::MiscWidgetsTab::new()),
Box::new(tabs::RecipeTab::new(0)),
],
})
}

pub fn run(&mut self) -> Result<()> {
tui::setup()?;
while !self.should_quit {
self.draw()?;
self.handle_events()?;
}
tui::restore()?;
Ok(())
}

fn draw(&mut self) -> Result<()> {
self.terminal
.draw(|frame| {
let titles = self.tabs.iter().map(|tab| tab.title()).collect_vec();
let tab: Box<dyn Tab> = {
// This is a bit of a hack to get around the borrow checker.
// which works because we know that the tabs are all static.
match self.tab_index {
0 => Box::new(AboutTab::new()),
1 => Box::new(EmailTab::new(self.selected_index)),
2 => Box::new(TracerouteTab::new(self.selected_index)),
3 => Box::new(MiscWidgetsTab::new()),
4 => Box::new(RecipeTab::new(self.selected_index)),
_ => unreachable!(),
}
};
let view = AppWidget::new(tab, self.tab_index, titles);
let area = frame.size();
frame.render_widget(view, area)
})
.context("terminal.draw")?;
Ok(())
}

fn handle_events(&mut self) -> Result<()> {
match tui::next_event(Duration::from_millis(16))? {
Some(Event::Key(key)) => self.handle_key_event(key),
_ => Ok(()),
}
}

fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
if key.kind != KeyEventKind::Press {
return Ok(());
}
match key.code {
KeyCode::Char('q') => {
self.should_quit = true;
}
KeyCode::Tab | KeyCode::BackTab if key.modifiers.contains(KeyModifiers::SHIFT) => {
let tab_index = self.tab_index + self.tabs.len(); // to wrap around properly
self.tab_index = tab_index.saturating_sub(1) % self.tabs.len();
self.selected_index = 0;
}
KeyCode::Tab | KeyCode::BackTab => {
self.tab_index = self.tab_index.saturating_add(1) % self.tabs.len();
self.selected_index = 0;
}
KeyCode::Left | KeyCode::Char('h') => {}
KeyCode::Right | KeyCode::Char('l') => {}
KeyCode::Up | KeyCode::Char('k') => {
self.selected_index = self.selected_index.saturating_sub(1);
}
KeyCode::Down | KeyCode::Char('j') => {
self.selected_index = self.selected_index.saturating_add(1);
}
_ => {}
};
Ok(())
}
}

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

pub fn install_panic_hook() {
better_panic::install();
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let _ = tui::restore();
hook(info);
std::process::exit(1);
}));
}

0 comments on commit e4c3732

Please sign in to comment.