feat(logo): Add a Ratatui logo widget

This is a simple logo widget that can be used to render the Ratatui logo
in the terminal. It is used in the `examples/ratatui-logo.rs` example,
and may be used in your applications' help or about screens.

```rust
use ratatui::{Frame, widgets::RatatuiLogo};

fn draw(frame: &mut Frame) {
    frame.render_widget(RatatuiLogo::tiny(), frame.area());
}
```
This commit is contained in:
Josh McKinney 2024-10-05 17:35:43 -07:00 committed by GitHub
parent baf047f556
commit 2805dddf05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 273 additions and 47 deletions

View file

@ -27,6 +27,7 @@ cassowary = "0.3"
compact_str = "0.8.0" compact_str = "0.8.0"
crossterm = { version = "0.28.1", optional = true } crossterm = { version = "0.28.1", optional = true }
document-features = { version = "0.2.7", optional = true } document-features = { version = "0.2.7", optional = true }
indoc = "2"
instability = "0.3.1" instability = "0.3.1"
itertools = "0.13" itertools = "0.13"
lru = "0.12.0" lru = "0.12.0"

View file

@ -13,57 +13,42 @@
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples //! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md //! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use std::{ use std::env::args;
io::{self},
thread::sleep, use color_eyre::Result;
time::Duration, use crossterm::event::{self, Event};
use ratatui::{
layout::{Constraint, Layout},
widgets::{RatatuiLogo, RatatuiLogoSize},
DefaultTerminal, TerminalOptions, Viewport,
}; };
use indoc::indoc; fn main() -> Result<()> {
use itertools::izip; color_eyre::install()?;
use ratatui::{widgets::Paragraph, TerminalOptions, Viewport}; let terminal = ratatui::init_with_options(TerminalOptions {
/// A fun example of using half block characters to draw a logo
#[allow(clippy::many_single_char_names)]
fn logo() -> String {
let r = indoc! {"
"};
let a = indoc! {"
"};
let t = indoc! {"
"};
let u = indoc! {"
"};
let i = indoc! {"
"};
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
.map(|(r, a, t, u, i)| format!("{r:5}{a:5}{t:4}{a:5}{t:4}{u:5}{i:5}"))
.collect::<Vec<_>>()
.join("\n")
}
fn main() -> io::Result<()> {
let mut terminal = ratatui::init_with_options(TerminalOptions {
viewport: Viewport::Inline(3), viewport: Viewport::Inline(3),
}); });
terminal.draw(|frame| frame.render_widget(Paragraph::new(logo()), frame.area()))?; let size = match args().nth(1).as_deref() {
sleep(Duration::from_secs(5)); Some("small") => RatatuiLogoSize::Small,
Some("tiny") => RatatuiLogoSize::Tiny,
_ => RatatuiLogoSize::default(),
};
let result = run(terminal, size);
ratatui::restore(); ratatui::restore();
println!(); println!();
Ok(()) result
}
fn run(mut terminal: DefaultTerminal, size: RatatuiLogoSize) -> Result<()> {
loop {
terminal.draw(|frame| {
use Constraint::{Fill, Length};
let [top, bottom] = Layout::vertical([Length(1), Fill(1)]).areas(frame.area());
frame.render_widget("Powered by", top);
frame.render_widget(RatatuiLogo::new(size), bottom);
})?;
if matches!(event::read()?, Event::Key(_)) {
break Ok(());
}
}
} }

View file

@ -10,3 +10,5 @@ Enter
Sleep 2s Sleep 2s
Show Show
Sleep 2s Sleep 2s
Hide
Escape

View file

@ -31,6 +31,7 @@ mod chart;
mod clear; mod clear;
mod gauge; mod gauge;
mod list; mod list;
mod logo;
mod paragraph; mod paragraph;
mod reflow; mod reflow;
mod scrollbar; mod scrollbar;
@ -46,6 +47,7 @@ pub use self::{
clear::Clear, clear::Clear,
gauge::{Gauge, LineGauge}, gauge::{Gauge, LineGauge},
list::{List, ListDirection, ListItem, ListState}, list::{List, ListDirection, ListItem, ListState},
logo::{RatatuiLogo, Size as RatatuiLogoSize},
paragraph::{Paragraph, Wrap}, paragraph::{Paragraph, Wrap},
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState}, scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
sparkline::{RenderDirection, Sparkline}, sparkline::{RenderDirection, Sparkline},

236
src/widgets/logo.rs Normal file
View file

@ -0,0 +1,236 @@
use indoc::indoc;
use crate::{buffer::Buffer, layout::Rect, text::Text, widgets::Widget};
/// A widget that renders the Ratatui logo
///
/// The Ratatui logo takes up two lines of text and comes in two sizes: `Tiny` and `Small`. This may
/// be used in an application's help or about screen to show that it is powered by Ratatui.
///
/// # Examples
///
/// The [Ratatui-logo] example demonstrates how to use the `RatatuiLogo` widget. This can be run by
/// cloning the Ratatui repository and then running the following command with an optional size
/// argument:
///
/// ```shell
/// cargo run --example ratatui-logo [size]
/// ```
///
/// [Ratatui-logo]: https://github.com/ratatui/ratatui/blob/main/examples/ratatui-logo.rs
///
/// ## Tiny (default, 2x15 characters)
///
/// ```
/// use ratatui::widgets::RatatuiLogo;
///
/// # fn draw(frame: &mut ratatui::Frame) {
/// frame.render_widget(RatatuiLogo::tiny(), frame.area());
/// # }
/// ```
///
/// Renders:
///
/// ```text
/// ▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
/// ▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌
/// ```
///
/// ## Small (2x27 characters)
///
/// ```
/// use ratatui::widgets::RatatuiLogo;
///
/// # fn draw(frame: &mut ratatui::Frame) {
/// frame.render_widget(RatatuiLogo::small(), frame.area());
/// # }
/// ```
///
/// Renders:
///
/// ```text
/// █▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █
/// █▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █
/// ```
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct RatatuiLogo {
size: Size,
}
/// The size of the logo
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Size {
/// A tiny logo
///
/// The default size of the logo (2x15 characters)
///
/// ```text
/// ▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
/// ▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌
/// ```
#[default]
Tiny,
/// A small logo
///
/// A slightly larger version of the logo (2x27 characters)
///
/// ```text
/// █▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █
/// █▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █
/// ```
Small,
}
impl RatatuiLogo {
/// Create a new Ratatui logo widget
///
/// # Examples
///
/// ```
/// use ratatui::widgets::{RatatuiLogo, RatatuiLogoSize};
///
/// let logo = RatatuiLogo::new(RatatuiLogoSize::Tiny);
/// ```
pub const fn new(size: Size) -> Self {
Self { size }
}
/// Set the size of the logo
///
/// # Examples
///
/// ```
/// use ratatui::widgets::{RatatuiLogo, RatatuiLogoSize};
///
/// let logo = RatatuiLogo::default().size(RatatuiLogoSize::Small);
/// ```
#[must_use]
pub const fn size(self, size: Size) -> Self {
let _ = self;
Self { size }
}
/// Create a new Ratatui logo widget with a tiny size
///
/// # Examples
///
/// ```
/// use ratatui::widgets::RatatuiLogo;
///
/// let logo = RatatuiLogo::tiny();
/// ```
pub const fn tiny() -> Self {
Self::new(Size::Tiny)
}
/// Create a new Ratatui logo widget with a small size
///
/// # Examples
///
/// ```
/// use ratatui::widgets::RatatuiLogo;
///
/// let logo = RatatuiLogo::small();
/// ```
pub const fn small() -> Self {
Self::new(Size::Small)
}
}
impl Widget for RatatuiLogo {
fn render(self, area: Rect, buf: &mut Buffer) {
let logo = self.size.as_str();
Text::raw(logo).render(area, buf);
}
}
impl Size {
const fn as_str(self) -> &'static str {
match self {
Self::Tiny => Self::tiny(),
Self::Small => Self::small(),
}
}
const fn tiny() -> &'static str {
indoc! {"
"}
}
const fn small() -> &'static str {
indoc! {"
"}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::tiny(Size::Tiny)]
#[case::small(Size::Small)]
fn new_size(#[case] size: Size) {
let logo = RatatuiLogo::new(size);
assert_eq!(logo.size, size);
}
#[test]
fn default_logo_is_tiny() {
let logo = RatatuiLogo::default();
assert_eq!(logo.size, Size::Tiny);
}
#[test]
fn set_logo_size_to_small() {
let logo = RatatuiLogo::default().size(Size::Small);
assert_eq!(logo.size, Size::Small);
}
#[test]
fn tiny_logo_constant() {
let logo = RatatuiLogo::tiny();
assert_eq!(logo.size, Size::Tiny);
}
#[test]
fn small_logo_constant() {
let logo = RatatuiLogo::small();
assert_eq!(logo.size, Size::Small);
}
#[test]
#[rustfmt::skip]
fn render_tiny() {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 2));
RatatuiLogo::tiny().render(buf.area, &mut buf);
assert_eq!(
buf,
Buffer::with_lines([
"▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌",
"▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌",
])
);
}
#[test]
#[rustfmt::skip]
fn render_small() {
let mut buf = Buffer::empty(Rect::new(0, 0, 27, 2));
RatatuiLogo::small().render(buf.area, &mut buf);
assert_eq!(
buf,
Buffer::with_lines([
"█▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█ █ █",
"█▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █",
])
);
}
}