Crate ratatui_garnish

Crate ratatui_garnish 

Source
Expand description

Garnish your Widgets

A powerful composition system for Ratatui widgets.

ratatui-garnish provides a flexible way to change the rendering of any Ratatui widget with garnishes like borders, titles, padding, shadows, and styling. Garnishes can be layered in any order, applied at runtime, and modified without altering the underlying widget. The GarnishedWidget struct wraps a widget and a Vec of Garnish enums, maintaining zero-cost abstractions and type safety without trait objects.

Want a margin outside a border? Garnish with Padding before a border. Need multiple borders or titles? Simply add them! Writing custom widgets but want to avoid boilerplate for styling or borders? Use ratatui-garnish with any widget implementing Widget or StatefulWidget.

§Example

use ratatui::{text::Text, style::{Color, Style}};
use ratatui_garnish::GarnishableWidget;
use ratatui_garnish::{border::RoundedBorder, title::{Title, Above}, Padding};

// Create a text widget with multiple decorations
let widget = Text::raw("Hello, World!\nTasty TUIs from Ratatui")
    .garnish(RoundedBorder::default())           // Add a rounded border
    .garnish(Title::<Above>::raw("My App"))      // Add a title above
    .garnish(Style::default().bg(Color::Blue))   // Set a background color
    .garnish(Padding::uniform(1));               // Add padding inside

// Garnishes are applied in order during rendering

§Getting Started

Import the GarnishableWidget trait to enable the garnish method on any Ratatui widget:

use ratatui_garnish::GarnishableWidget;

This trait extends Widget. There is a similar traits GarnishableStatefulWidget for StatefulWidget. The RenderModifier trait defines how garnishes modify rendering and layout. Ratatui’s Style implements RenderModifier and Padding too (which is similair to the Padding from Block but can be serialized) allowing their use as garnishes:

use ratatui::{style::{Color, Style}, text::Line};
use ratatui_garnish::{GarnishableWidget, RenderModifier, Padding};

let widget = Line::raw("Hello, World!")
    .garnish(Style::default().bg(Color::Blue))   // Background for padded area
    .garnish(Padding::horizontal(1))             // Padding on left and right
    .garnish(Style::default().bg(Color::Red))    // Background for next padded area
    .garnish(Padding::vertical(2))               // Padding on top and bottom
    .garnish(Style::default().bg(Color::White)); // Background for the line

The first call to garnish() returns a GarnishedWidget or GarnishedStatefulWidget, which wraps your widget and a Vec of Garnish. It also has a garnish() method so you can keep adding garnishes. At any time you can access the garnishes you’ve added by treating GarnishedWidget like a Vec of Garnish items, which is an enum that wraps all available garnishes.

let widget = Line::raw("Hello, World!")
   .garnish(Style::default().bg(Color::Blue))
   .garnish(Padding::horizontal(1));

assert!(widget[0].is_style()); // The first garnish we added
assert_eq!(widget.first_padding(), Some(&Padding::horizontal(1)));

// Let's look at all the garnishes
for garnish in &widget {
    println!("{garnish:?}");
}

Alternatively you can create a GarnishedWidget from a widget and a garnish using the new constructor or from only a widget using from. Add garnishes with methods like push or extend.

use ratatui_garnish::{GarnishedWidget, RenderModifier, Padding};
use ratatui::{style::{Style, Color}, text::Line};

let mut widget = GarnishedWidget::from(Line::raw("Hello, World!"));
widget.push(Style::default().bg(Color::Green));

§Available Garnishes

§Borders

§Titles

§Shadows

  • Shadow (light , medium , dark , or full shades with full-character offsets)
  • HalfShadow (full or quadrant characters with half-character offsets)

§Padding

  • Padding (spacing around the widget), same as Padding from ratatui::widgets::Block

§Built-in Ratatui Support

  • Style (background colors, text styling)

§Complex Compositions

Combine multiple garnishes for rich widget designs:

use ratatui_garnish::{
    GarnishableWidget, RenderModifier,
    title::{Title, Top, Bottom,},
    border::DoubleBorder, Padding
};
use ratatui::{
    text::Line,
    style::{Color, Style, Modifier},
};

let complex_widget = Line::raw("Important Message")
    // Add a margin
    .garnish(Padding::uniform(2))
    // Set Background color
    .garnish(Style::default().bg(Color::DarkGray))
    // Border with title
    .garnish(Title::<Top>::styled("⚠ WARNING ⚠",
        Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)).margin(1))
    .garnish(Title::<Bottom>::raw("Status: Active").right_aligned().margin(1))
    .garnish(DoubleBorder::default())
    .garnish(Padding::uniform(1));

§Reusing Garnishes

Use the Garnishes vec and extend_from_slice or extend to apply the same garnishes to multiple widgets:


let garnishes = garnishes![
    Style::default().fg(Color::Blue),
    DoubleBorder::default(),
    Padding::uniform(2),
    Style::default().fg(Color::White),
];

let mut widget = GarnishedWidget::from(Line::raw("First widget"));
widget.extend_from_slice(&garnishes);

let mut other_widget = Line::raw("Other widget")
    .garnish(Title::<Top>::styled("Second",
        Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)).margin(1));
other_widget.extend(garnishes);

The GarnishableWidget and GarnishableStatefulWidget add the methods garnishes and garnishes_from_slice to construct GarnishedWidgets directly from Garnishes.


let widget = Line::raw("Widget")
    .garnishes( garnishes![
    Style::default().fg(Color::Blue),
    DoubleBorder::default(),
    Padding::uniform(2),
    Style::default().fg(Color::White),
]);

// copy garnishes of widget to other_widget
let other_widget = Line::raw("Other Widget")
    .garnishes_from_slice(widget.as_slice());

§Features

§Serde support

Serialization & deserialization using serde can be enabled using the cargo feature serde. When it is enabled all garnishes, the Garnish enum and the Garnishes Vec can be serialized and deserialized. This makes it easy to add theme support to your application.

§Decorated widget

The cargo feature decorated widget enables DecoratedWidget and DecoratedStatefulWidget which wrap one widget with one garnish, like the traditional decorator pattern. It offers little benefits over GarnishedWidget. It might be slightly faster if you want to use only a small number of garnishes. The after_render functions are rendered in reverse order.

use ratatui::{style::{Color, Style}, text::Text};
use ratatui_garnish::{
    border::PlainBorder,
    title::{Title, Top},
    GarnishableWidget, Padding,
};

#[cfg(feature = "decorated_widget")]
let widget = Text::raw("Hello World!")
    .decorate(Style::default().fg(Color::Red).bg(Color::White))
    .decorate(Title::<Top>::raw("Paragraph").margin(1))
    .decorate(PlainBorder::default())
    .decorate(Padding::horizontal(2));

§Compatibility

ratatui-garnish works seamlessly with any Ratatui widget implementing Widget or StatefulWidget, following Ratatui’s conventions.

§Contributing

This is the first release of ratatui-garnish. More garnishes are planned, and contributions are welcome!

Modules§

border
Border Garnishes
shadow
Shadow garnishes
title
A garnish that adds a styled text line to a widget at a specified position.

Macros§

garnishes

Structs§

GarnishedStatefulWidget
A widget that wraps another stateful widget with a vec of garnishes.
GarnishedWidget
A widget that wraps another widget with a vector of garnishes.
Garnishes
A Vec of Garnish for applying multiple garnishes to widgets.
Padding
Padding garnish.

Enums§

Garnish
Enum wrapping all available garnishes.

Traits§

GarnishableStatefulWidget
A trait for stateful widgets that can be garnished.
GarnishableWidget
A trait for widgets that can be garnished.
RenderModifier
A trait that can modify the rendering of a widget.