Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): add demo2 example #500

Merged
merged 1 commit into from Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
12 changes: 9 additions & 3 deletions Cargo.toml
Expand Up @@ -48,14 +48,15 @@ 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"
rand = "0.8.5"
palette = "0.7.3"
pretty_assertions = "1.4.0"

[features]
Expand Down Expand Up @@ -149,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"
required-features = ["crossterm", "widget-calendar"]
doc-scrape-examples = true

[[example]]
name = "gauge"
required-features = ["crossterm"]
Expand Down
33 changes: 23 additions & 10 deletions README.md
@@ -1,23 +1,36 @@
# Ratatui
![Demo of
Ratatui](https://raw.githubusercontent.com/ratatui-org/ratatui/aa09e59dc0058347f68d7c1e0c91f863c6f2b8c9/examples/demo2.gif)
<!--
Permalink to https://github.com/ratatui-org/ratatui/blob/images/examples/demo2.gif
See RELEASE.md for instructions on creating the demo gif
--->

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

`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)

</div>

<!-- See RELEASE.md for instructions on creating the demo gif --->
![Demo of Ratatui](https://vhs.charm.sh/vhs-tF0QbuPbtHgUeG0sTVgFr.gif)
<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
10 changes: 6 additions & 4 deletions RELEASE.md
Expand Up @@ -7,12 +7,14 @@ actions](.github/workflows/cd.yml) and triggered by pushing a tag.
[vhs](https://github.com/charmbracelet/vhs) (installation instructions in README).

```shell
cargo build --example demo
vhs examples/demo.tape --publish --quiet
cargo build --example demo2
vhs examples/demo2.tape
```

Then update the link in the [examples README](./examples/README) and the main README. Avoid
adding the gif to the git repo as binary files tend to bloat repositories.
1. Switch branches to the images branch and copy demo2.gif to examples/, commit, and push.
1. Grab the permalink from <https://github.com/ratatui-org/ratatui/blob/images/examples/demo2.gif> and
append `?raw=true` to redirect to the actual image url. Then update the link in the main README.
Avoid adding the gif to the git repo as binary files tend to bloat repositories.

1. Bump the version in [Cargo.toml](Cargo.toml).
1. Bump versions in the doc comments of [lib.rs](src/lib.rs).
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Expand Up @@ -8,7 +8,7 @@ occur in a terminal.

## Demo

This is the demo example from the main README. It is available for each of the backends. Source:
This is the previous demo example from the main README. It is available for each of the backends. Source:
[demo.rs](./demo/).

```shell
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
43 changes: 43 additions & 0 deletions examples/demo2-social.tape
@@ -0,0 +1,43 @@
# 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-social.gif"
Set Theme "OceanicMaterial"
# Github social preview size (1280x640 with 80px padding) and must be < 1MB
# This puts some constraints on the amount of interactivity we can do.
Set Width 1280
Set Height 640
Set Padding 80
Hide
Type "cargo run --example demo2 --features crossterm,widget-calendar"
Enter
Sleep 2s
Show
# About screen
Sleep 3.5s
Down # Red eye
Sleep 0.4s
Down # black eye
Sleep 1s
Tab
# Recipe
Sleep 1s
Set TypingSpeed 500ms
Down 7
Sleep 1s
Tab
# Email
Sleep 2s
Down 4
Sleep 2s
Tab
# Trace route
Sleep 1s
Set TypingSpeed 200ms
Down 10
Sleep 2s
Tab
# Weather
Set TypingSpeed 100ms
Down 40
Sleep 2s
49 changes: 49 additions & 0 deletions examples/demo2.tape
@@ -0,0 +1,49 @@
# 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`
# NOTE: Requires VHS 0.6.1 or later for Screenshot support
Output "target/demo2.gif"
Set Theme "OceanicMaterial"
# The reason for this strange size is that the social preview image for this
# demo is 1280x64 with 80 pixels of padding on each side. We want a version
# without the padding for README.md, etc.
Set Width 1120
Set Height 480
Set Padding 0
Hide
Type "cargo run --example demo2 --features crossterm,widget-calendar"
Enter
Sleep 2s
Show
# About screen
Screenshot "target/demo2-about.png"
Sleep 3.5s
Down # Red eye
Sleep 0.4s
Down # black eye
Sleep 1s
Tab
# Recipe
Screenshot "target/demo2-recipe.png"
Sleep 1s
Set TypingSpeed 500ms
Down 7
Sleep 1s
Tab
# Email
Screenshot "target/demo2-email.png"
Sleep 2s
Down 4
Sleep 2s
Tab
# Trace route
Screenshot "target/demo2-trace.png"
Sleep 1s
Set TypingSpeed 200ms
Down 10
Sleep 2s
Tab
# Weather
Screenshot "target/demo2-weather.png"
Set TypingSpeed 100ms
Down 40
Sleep 2s
99 changes: 99 additions & 0 deletions examples/demo2/app.rs
@@ -0,0 +1,99 @@
use std::time::Duration;

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

use crate::{Root, Term};

#[derive(Debug)]
pub struct App {
term: Term,
should_quit: bool,
context: AppContext,
joshka marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Default, Clone, Copy)]
pub struct AppContext {
pub tab_index: usize,
pub row_index: usize,
}

impl App {
fn new() -> Result<Self> {
Ok(Self {
term: Term::start()?,
should_quit: false,
context: AppContext::default(),
})
}

pub fn run() -> Result<()> {
install_panic_hook();
let mut app = Self::new()?;
while !app.should_quit {
app.draw()?;
app.handle_events()?;
}
Term::stop()?;
Ok(())
}

fn draw(&mut self) -> Result<()> {
self.term
.draw(|frame| frame.render_widget(Root::new(&self.context), frame.size()))
.context("terminal.draw")?;
Ok(())
}

fn handle_events(&mut self) -> Result<()> {
match Term::next_event(Duration::from_millis(16))? {
Some(Event::Key(key)) => self.handle_key_event(key),
Some(Event::Resize(width, height)) => {
Ok(self.term.resize(Rect::new(0, 0, width, height))?)
}
_ => Ok(()),
}
}

fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
if key.kind != KeyEventKind::Press {
return Ok(());
}

let context = &mut self.context;
const TAB_COUNT: usize = 5;
match key.code {
KeyCode::Char('q') | KeyCode::Esc => {
self.should_quit = true;
}
KeyCode::Tab | KeyCode::BackTab if key.modifiers.contains(KeyModifiers::SHIFT) => {
let tab_index = context.tab_index + TAB_COUNT; // to wrap around properly
context.tab_index = tab_index.saturating_sub(1) % TAB_COUNT;
context.row_index = 0;
}
KeyCode::Tab | KeyCode::BackTab => {
context.tab_index = context.tab_index.saturating_add(1) % TAB_COUNT;
context.row_index = 0;
}
KeyCode::Up | KeyCode::Char('k') => {
context.row_index = context.row_index.saturating_sub(1);
}
KeyCode::Down | KeyCode::Char('j') => {
context.row_index = context.row_index.saturating_add(1);
}
_ => {}
};
Ok(())
}
}

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