ratatui_garnish/
lib.rs

1//! <div align="center">
2//!
3//! *Garnish your Widgets*
4//!
5//! </div>
6//!
7//! A powerful composition system for [Ratatui](https://ratatui.rs) widgets.
8//!
9//! `ratatui-garnish` provides a flexible way to change the rendering of any Ratatui widget with
10//! garnishes like borders, titles, padding, shadows, and styling. Garnishes can be layered
11//! in any order, applied at runtime, and modified without altering the underlying widget. The
12//! `GarnishedWidget` struct wraps a widget and a `Vec` of `Garnish` enums, maintaining zero-cost
13//! abstractions and type safety without trait objects.
14//!
15//! Want a margin outside a border? Garnish with `Padding` before a border. Need multiple borders or
16//! titles? Simply add them! Writing custom widgets but want to avoid boilerplate for styling or
17//! borders? Use `ratatui-garnish` with any widget implementing `Widget` or `StatefulWidget`.
18//!
19//! # Example
20//!
21//! ```rust
22//! use ratatui::{text::Text, style::{Color, Style}};
23//! use ratatui_garnish::GarnishableWidget;
24//! use ratatui_garnish::{border::RoundedBorder, title::{Title, Above}, Padding};
25//!
26//! // Create a text widget with multiple decorations
27//! let widget = Text::raw("Hello, World!\nTasty TUIs from Ratatui")
28//!     .garnish(RoundedBorder::default())           // Add a rounded border
29//!     .garnish(Title::<Above>::raw("My App"))      // Add a title above
30//!     .garnish(Style::default().bg(Color::Blue))   // Set a background color
31//!     .garnish(Padding::uniform(1));               // Add padding inside
32//!
33//! // Garnishes are applied in order during rendering
34//! ```
35//!
36//! # Getting Started
37//!
38//! Import the `GarnishableWidget` trait to enable the `garnish` method on any Ratatui widget:
39//!
40//! ```rust
41//! use ratatui_garnish::GarnishableWidget;
42//! ```
43//!
44//! This trait extends `Widget`. There is a similar traits `GarnishableStatefulWidget` for `StatefulWidget`.
45//! The [`RenderModifier`] trait defines how garnishes modify rendering and layout. Ratatui's
46//! `Style` implements `RenderModifier` and [`Padding`] too (which is similair to the
47//! `Padding` from `Block` but can be serialized) allowing their use as garnishes:
48//!
49//! ```rust
50//! use ratatui::{style::{Color, Style}, text::Line};
51//! use ratatui_garnish::{GarnishableWidget, RenderModifier, Padding};
52//!
53//! let widget = Line::raw("Hello, World!")
54//!     .garnish(Style::default().bg(Color::Blue))   // Background for padded area
55//!     .garnish(Padding::horizontal(1))             // Padding on left and right
56//!     .garnish(Style::default().bg(Color::Red))    // Background for next padded area
57//!     .garnish(Padding::vertical(2))               // Padding on top and bottom
58//!     .garnish(Style::default().bg(Color::White)); // Background for the line
59//! ```
60//!
61//! The first call to `garnish()` returns a [`GarnishedWidget`] or [`GarnishedStatefulWidget`], which wraps your
62//! widget and a `Vec` of [`Garnish`]. It also has a `garnish()` method so you
63//! can keep adding garnishes. At any time you can access the
64//! garnishes you've added by treating `GarnishedWidget` like a `Vec` of
65//! `Garnish` items, which is an enum that wraps all available garnishes.
66//!
67//! ```rust
68//! # use ratatui_garnish::{GarnishableWidget, RenderModifier, Padding};
69//! # use ratatui::{style::{Style, Color}, text::Line};
70//! #
71//! let widget = Line::raw("Hello, World!")
72//!    .garnish(Style::default().bg(Color::Blue))
73//!    .garnish(Padding::horizontal(1));
74//!
75//! assert!(widget[0].is_style()); // The first garnish we added
76//! assert_eq!(widget.first_padding(), Some(&Padding::horizontal(1)));
77//!
78//! // Let's look at all the garnishes
79//! for garnish in &widget {
80//!     println!("{garnish:?}");
81//! }
82//! ```
83//!
84//! Alternatively you can create a `GarnishedWidget` from a widget and a garnish using the `new` constructor
85//! or from only a widget using `from`.  Add garnishes with methods like `push` or `extend`.
86//!
87//! ```rust
88//! use ratatui_garnish::{GarnishedWidget, RenderModifier, Padding};
89//! use ratatui::{style::{Style, Color}, text::Line};
90//!
91//! let mut widget = GarnishedWidget::from(Line::raw("Hello, World!"));
92//! widget.push(Style::default().bg(Color::Green));
93//! ````
94//
95//! # Available Garnishes
96//!
97//! ## Borders
98//! - Standard: [`PlainBorder`], [`RoundedBorder`], [`DoubleBorder`], [`ThickBorder`]
99//! - Dashed variants: [`DashedBorder`], [`RoundedDashedBorder`], [`ThickDashedBorder`],
100//! - Custom: [`CharBorder`] (single character, e.g., `****`), [`CustomBorder`] (fully customizable character set)
101//! - Specialty: [`QuadrantInsideBorder`], [`QuadrantOutsideBorder`], [`FatInsideBorder`], [`FatOutsideBorder`]
102//!
103//! ## Titles
104//! - Horizontal: [`Title<Top>`] (over top border), [`Title<Bottom>`] (over bottom border), [`Title<Above>`] (reserves space above), [`Title<Below>`] (reserves space below)
105//! - Vertical: [`Title<Left>`] (over left border), [`Title<Right>`] (over right border), [`Title<Before>`] (reserves space left), [`Title<After>`] (reserves space right)
106//!
107//! ## Shadows
108//! - [`Shadow`] (light `░`, medium `▒`, dark `▓`, or full `█` shades with full-character offsets)
109//! - [`HalfShadow`] (full `█` or quadrant characters with half-character offsets)
110//!
111//! ## Padding
112//! - [`Padding`] (spacing around the widget), same as `Padding` from `ratatui::widgets::Block`
113//!
114//! ## Built-in Ratatui Support
115//! - [`Style`] (background colors, text styling)
116//!
117//! ## Complex Compositions
118//!
119//! Combine multiple garnishes for rich widget designs:
120//!
121//! ```rust
122//! use ratatui_garnish::{
123//!     GarnishableWidget, RenderModifier,
124//!     title::{Title, Top, Bottom,},
125//!     border::DoubleBorder, Padding
126//! };
127//! use ratatui::{
128//!     text::Line,
129//!     style::{Color, Style, Modifier},
130//! };
131//!
132//! let complex_widget = Line::raw("Important Message")
133//!     // Add a margin
134//!     .garnish(Padding::uniform(2))
135//!     // Set Background color
136//!     .garnish(Style::default().bg(Color::DarkGray))
137//!     // Border with title
138//!     .garnish(Title::<Top>::styled("⚠ WARNING ⚠",
139//!         Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)).margin(1))
140//!     .garnish(Title::<Bottom>::raw("Status: Active").right_aligned().margin(1))
141//!     .garnish(DoubleBorder::default())
142//!     .garnish(Padding::uniform(1));
143//! ```
144//!
145//! # Reusing Garnishes
146//!
147//! Use the [`Garnishes`] vec and `extend_from_slice` or `extend` to apply
148//! the same garnishes to multiple widgets:
149//!
150//! ```rust
151//! # use ratatui_garnish::{
152//! #     GarnishedWidget, GarnishableWidget, RenderModifier,
153//! #     Padding,
154//! #     title::{Title, Top},
155//! #     border::DoubleBorder, garnishes,
156//! # };
157//! # use ratatui::{
158//! #     text::Line,
159//! #     style::{Color, Style, Modifier},
160//! # };
161//!
162//! let garnishes = garnishes![
163//!     Style::default().fg(Color::Blue),
164//!     DoubleBorder::default(),
165//!     Padding::uniform(2),
166//!     Style::default().fg(Color::White),
167//! ];
168//!
169//! let mut widget = GarnishedWidget::from(Line::raw("First widget"));
170//! widget.extend_from_slice(&garnishes);
171//!
172//! let mut other_widget = Line::raw("Other widget")
173//!     .garnish(Title::<Top>::styled("Second",
174//!         Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)).margin(1));
175//! other_widget.extend(garnishes);
176//! ```
177//!
178//! The `GarnishableWidget` and `GarnishableStatefulWidget` add the methods
179//! `garnishes` and `garnishes_from_slice` to construct `GarnishedWidget`s directly
180//! from [`Garnishes`].
181//!
182//! ```rust
183//! # use ratatui_garnish::{
184//! #     GarnishedWidget, GarnishableWidget, RenderModifier,
185//! #     Padding,
186//! #     border::DoubleBorder, garnishes,
187//! # };
188//! # use ratatui::{
189//! #     text::Line,
190//! #     style::{Color, Style },
191//! # };
192//!
193//! let widget = Line::raw("Widget")
194//!     .garnishes( garnishes![
195//!     Style::default().fg(Color::Blue),
196//!     DoubleBorder::default(),
197//!     Padding::uniform(2),
198//!     Style::default().fg(Color::White),
199//! ]);
200//!
201//! // copy garnishes of widget to other_widget
202//! let other_widget = Line::raw("Other Widget")
203//!     .garnishes_from_slice(widget.as_slice());
204//! ```
205//!
206//! # Features
207//!
208//! ## Serde support
209//!
210//! Serialization & deserialization using serde can be enabled using the cargo feature
211//! `serde`. When it is enabled all garnishes, the `Garnish` enum and the `Garnishes`
212//! `Vec` can be serialized and deserialized. This makes it easy to add theme support
213//! to your application.
214//!
215//! ## Decorated widget
216//!
217//! The cargo feature `decorated widget` enables `DecoratedWidget` and `DecoratedStatefulWidget`
218//! which wrap one widget with one garnish, like the traditional decorator pattern. It
219//! offers little benefits over `GarnishedWidget`. It might be slightly faster
220//! if you want to use only a small number of garnishes. The `after_render` functions
221//! are rendered in reverse order.
222//!
223//! ```rust
224//! use ratatui::{style::{Color, Style}, text::Text};
225//! use ratatui_garnish::{
226//!     border::PlainBorder,
227//!     title::{Title, Top},
228//!     GarnishableWidget, Padding,
229//! };
230//!
231//! #[cfg(feature = "decorated_widget")]
232//! let widget = Text::raw("Hello World!")
233//!     .decorate(Style::default().fg(Color::Red).bg(Color::White))
234//!     .decorate(Title::<Top>::raw("Paragraph").margin(1))
235//!     .decorate(PlainBorder::default())
236//!     .decorate(Padding::horizontal(2));
237//! ```
238//!
239//! # Compatibility
240//!
241//! `ratatui-garnish` works seamlessly with any Ratatui widget implementing `Widget` or `StatefulWidget`,
242//! following Ratatui's conventions.
243//!
244//! # Contributing
245//!
246//! This is the first release of `ratatui-garnish`. More garnishes are planned, and contributions are
247//! welcome!
248
249use derive_more::{Deref, DerefMut};
250use ratatui::{
251    buffer::Buffer,
252    layout::Rect,
253    style::Style,
254    widgets::{StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
255};
256
257pub mod border;
258#[cfg(feature = "decorated_widget")]
259mod decorator;
260mod padding;
261pub mod shadow;
262pub mod title;
263
264#[cfg(feature = "decorated_widget")]
265pub use decorator::{DecoratedStatefulWidget, DecoratedWidget};
266pub use padding::Padding;
267
268use border::{
269    CharBorder, CustomBorder, DashedBorder, DoubleBorder, FatInsideBorder, FatOutsideBorder,
270    PlainBorder, QuadrantInsideBorder, QuadrantOutsideBorder, RoundedBorder, RoundedDashedBorder,
271    ThickBorder, ThickDashedBorder,
272};
273use shadow::{HalfShadow, Shadow};
274use title::{Above, After, Before, Below, Bottom, Left, Right, Title, Top};
275
276/// A trait that can modify the rendering of a widget.
277pub trait RenderModifier {
278    /// Modifies the widget's rendering area.
279    ///
280    /// Returns the adjusted area, typically reduced to account for borders, padding, or shadows.
281    /// Default implementation returns the input area unchanged.
282    fn modify_area(&self, area: Rect) -> Rect {
283        area
284    }
285
286    /// Executes before the widget is rendered.
287    ///
288    /// Used for pre-rendering effects like setting background styles or drawing shadows.
289    /// Default implementation does nothing.
290    fn before_render(&self, _area: Rect, _buf: &mut Buffer) {}
291
292    /// Executes after the widget is rendered.
293    ///
294    /// Used for post-rendering effects like drawing titles over borders.
295    /// Default implementation does nothing.
296    fn after_render(&self, _area: Rect, _buf: &mut Buffer) {}
297}
298
299nodyn::nodyn! {
300    /// Enum wrapping all available garnishes.
301    #[module_path = "ratatui_garnish"]
302    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
303    #[derive(Debug, Clone)]
304    pub enum Garnish<'a> {
305        CharBorder,
306        CustomBorder,
307        DashedBorder,
308        DoubleBorder,
309        FatInsideBorder,
310        FatOutsideBorder,
311        HalfShadow,
312        Padding,
313        PlainBorder,
314        QuadrantInsideBorder,
315        QuadrantOutsideBorder,
316        RoundedBorder,
317        RoundedDashedBorder,
318        Shadow,
319        Style,
320        ThickBorder,
321        ThickDashedBorder,
322        Title<'a, Above>,
323        Title<'a, After>,
324        Title<'a, Before>,
325        Title<'a, Below>,
326        Title<'a, Bottom>,
327        Title<'a, Left>,
328        Title<'a, Right>,
329        Title<'a, Top>,
330    }
331
332    impl is_as;
333
334    impl RenderModifier {
335        fn before_render(&self, area: Rect, buf: &mut Buffer);
336        fn modify_area(&self, area: Rect) -> Rect;
337        fn after_render(&self, area: Rect, buf: &mut Buffer);
338    }
339
340    /// A `Vec` of `Garnish` for applying multiple garnishes to widgets.
341    ///
342    /// Useful for reusing a set of garnishes across multiple widgets.
343    ///
344    /// # Example
345    ///
346    /// ```rust
347    /// use ratatui::{style::{Color, Style}, text::Line};
348    /// use ratatui_garnish::{
349    ///     border::DoubleBorder, garnishes, GarnishableWidget,
350    ///     title::{Title, Top},
351    /// };
352    ///
353    /// let garnishes = garnishes![
354    ///     Style::default().fg(Color::Blue),
355    ///     DoubleBorder::default(),
356    ///     Style::default().fg(Color::White),
357    /// ];
358    ///
359    /// let widget = Line::raw("Garnished Widget")
360    ///     .garnish(Title::<Top>::raw("Blue Border"))
361    ///     .extend_from_slice(&garnishes);
362    /// ```
363    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
364    #[cfg_attr(feature = "serde", serde(transparent))]
365    vec Garnishes;
366
367    /// A widget that wraps another widget with a vector of garnishes.
368    ///
369    /// This struct implements `Deref` and `DerefMut` to the inner widget,
370    /// allowing you to access the original widget's methods while adding
371    /// garnish functionality.
372    #[vec(garnishes)]
373    #[derive(Debug, Deref, DerefMut)]
374    pub struct GarnishedWidget<W> {
375        #[deref]
376        #[deref_mut]
377        pub widget: W,
378    }
379
380    /// A widget that wraps another stateful widget with a vec of garnishes.
381    ///
382    /// This struct implements `Deref` and `DerefMut` to the inner widget,
383    /// allowing you to access the original widget's methods while adding
384    /// garnish functionality.
385    #[vec(garnishes)]
386    #[derive(Debug, Deref, DerefMut)]
387    pub struct GarnishedStatefulWidget<W> {
388        #[deref]
389        #[deref_mut]
390        pub widget: W,
391    }
392}
393
394impl<'a, W> GarnishedWidget<'a, W> {
395    /// creates a new `garnishedwidget` with a single garnish.
396    ///
397    /// # example
398    ///
399    /// ```rust
400    /// use ratatui::{style::Style, text::Line};
401    /// use ratatui_garnish::GarnishedWidget;
402    ///
403    /// let widget = GarnishedWidget::new(Line::raw("Test"), Style::default());
404    /// ```
405    pub fn new<G: Into<Garnish<'a>>>(widget: W, garnish: G) -> Self {
406        Self {
407            widget,
408            garnishes: vec![garnish.into()],
409        }
410    }
411
412    /// Adds an additional garnish to the widget.
413    ///
414    /// # Example
415    ///
416    /// ```rust
417    /// use ratatui::{style::Style, text::Line};
418    /// use ratatui_garnish::{GarnishableWidget, Padding};
419    ///
420    /// let widget = Line::raw("Test")
421    ///     .garnish(Style::default())
422    ///     .garnish(Padding::uniform(1));
423    /// ```
424    #[must_use = "method returns a new instance and does not mutate the original"]
425    pub fn garnish<G: Into<Garnish<'a>>>(mut self, garnish: G) -> Self {
426        self.push(garnish);
427        self
428    }
429}
430
431impl<W: Widget> From<W> for GarnishedWidget<'_, W> {
432    fn from(value: W) -> Self {
433        Self {
434            widget: value,
435            garnishes: Vec::new(),
436        }
437    }
438}
439
440impl<W: Widget> Widget for GarnishedWidget<'_, W> {
441    fn render(self, area: Rect, buf: &mut Buffer) {
442        let mut render_area = area;
443        for g in &self.garnishes {
444            g.before_render(render_area, buf);
445            render_area = g.modify_area(render_area);
446        }
447
448        self.widget.render(render_area, buf);
449
450        let mut render_area = area;
451        for g in &self.garnishes {
452            g.after_render(render_area, buf);
453            render_area = g.modify_area(render_area);
454        }
455    }
456}
457
458impl<W: WidgetRef> WidgetRef for GarnishedWidget<'_, W> {
459    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
460        let mut render_area = area;
461        for g in &self.garnishes {
462            g.before_render(render_area, buf);
463            render_area = g.modify_area(render_area);
464        }
465
466        self.widget.render_ref(render_area, buf);
467
468        let mut render_area = area;
469        for g in &self.garnishes {
470            g.after_render(render_area, buf);
471            render_area = g.modify_area(render_area);
472        }
473    }
474}
475
476impl<'a, W> GarnishedStatefulWidget<'a, W> {
477    /// Creates a new `GarnishedStatefulWidget` with a single garnish.
478    ///
479    /// # Example
480    ///
481    /// ```rust
482    /// use ratatui::{style::Style, widgets::List, text::Line};
483    /// use ratatui_garnish::GarnishedStatefulWidget;
484    ///
485    /// let widget = GarnishedStatefulWidget::new(List::new::<Vec<Line>>(vec![]), Style::default());
486    /// ```
487    pub fn new<G: Into<Garnish<'a>>>(widget: W, garnish: G) -> Self {
488        Self {
489            widget,
490            garnishes: vec![garnish.into()],
491        }
492    }
493
494    /// Adds an additional garnish to the widget.
495    ///
496    /// # Example
497    ///
498    /// ```rust
499    /// use ratatui::{style::Style, widgets::List, text::Line};
500    /// use ratatui_garnish::{GarnishableWidget, Padding};
501    ///
502    /// let widget = List::new::<Vec<Line>>(vec![])
503    ///     .garnish(Style::default())
504    ///     .garnish(Padding::uniform(1));
505    /// ```
506    #[must_use = "method returns a new instance and does not mutate the original"]
507    pub fn garnish<G: Into<Garnish<'a>>>(mut self, garnish: G) -> Self {
508        self.push(garnish);
509        self
510    }
511}
512
513impl<W: StatefulWidget> From<W> for GarnishedStatefulWidget<'_, W> {
514    fn from(value: W) -> Self {
515        Self {
516            widget: value,
517            garnishes: Vec::new(),
518        }
519    }
520}
521
522impl<W> StatefulWidget for GarnishedStatefulWidget<'_, W>
523where
524    W: StatefulWidget,
525{
526    type State = W::State;
527
528    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
529        let mut render_area = area;
530        for g in &self.garnishes {
531            g.before_render(render_area, buf);
532            render_area = g.modify_area(render_area);
533        }
534
535        self.widget.render(render_area, buf, state);
536
537        let mut render_area = area;
538        for g in &self.garnishes {
539            g.after_render(render_area, buf);
540            render_area = g.modify_area(render_area);
541        }
542    }
543}
544
545impl<W> StatefulWidgetRef for GarnishedStatefulWidget<'_, W>
546where
547    W: StatefulWidgetRef,
548{
549    type State = W::State;
550
551    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
552        let mut render_area = area;
553        for g in &self.garnishes {
554            g.before_render(render_area, buf);
555            render_area = g.modify_area(render_area);
556        }
557
558        self.widget.render_ref(render_area, buf, state);
559
560        let mut render_area = area;
561        for g in &self.garnishes {
562            g.after_render(render_area, buf);
563            render_area = g.modify_area(render_area);
564        }
565    }
566}
567
568/// A trait for widgets that can be garnished.
569pub trait GarnishableWidget: Widget + Sized {
570    /// Applies a garnish to the widget, wrapping it in a `GarnishedWidget`.
571    fn garnish<'a, G: Into<Garnish<'a>>>(self, garnish: G) -> GarnishedWidget<'a, Self> {
572        GarnishedWidget::new(self, garnish)
573    }
574
575    /// Applies a Vec<Garnish>to the widget, wrapping it in a `GarnishedWidget`.
576    fn garnishes<'a, G: Into<Vec<Garnish<'a>>>>(self, garnishes: G) -> GarnishedWidget<'a, Self> {
577        GarnishedWidget {
578            widget: self,
579            garnishes: garnishes.into(),
580        }
581    }
582
583    /// Applies a copy of `&[Garnish]` to the widget, wrapping it in a `GarnishedWidget`.
584    fn garnishes_from_slice<'a>(self, garnishes: &[Garnish<'a>]) -> GarnishedWidget<'a, Self> {
585        GarnishedWidget {
586            widget: self,
587            garnishes: garnishes.to_vec(),
588        }
589    }
590
591    /// Applies a garnish to the widget, wrapping it in a `DecoratedWidget`.
592    #[cfg(feature = "decorated_widget")]
593    fn decorate<R: RenderModifier>(self, garnish: R) -> DecoratedWidget<Self, R> {
594        DecoratedWidget::new(self, garnish)
595    }
596}
597
598// Blanket implementation for all widgets that implement `Widget`.
599impl<W: Widget> GarnishableWidget for W {}
600
601/// A trait for stateful widgets that can be garnished.
602pub trait GarnishableStatefulWidget: StatefulWidget + Sized {
603    /// Applies a garnish to the widget, wrapping it in a `GarnishedStatefulWidget`.
604    fn garnish<'a, G: Into<Garnish<'a>>>(self, garnish: G) -> GarnishedStatefulWidget<'a, Self> {
605        GarnishedStatefulWidget::new(self, garnish)
606    }
607
608    /// Applies a Vec<Garnish>to the widget, wrapping it in a `GarnishedStatefulWidget`.
609    fn garnishes<'a, G: Into<Vec<Garnish<'a>>>>(
610        self,
611        garnishes: G,
612    ) -> GarnishedStatefulWidget<'a, Self> {
613        GarnishedStatefulWidget {
614            widget: self,
615            garnishes: garnishes.into(),
616        }
617    }
618
619    /// Applies a copy of `&[Garnish]` to the widget, wrapping it in a `GarnishedStatefulWidget`.
620    fn garnishes_from_slice<'a>(
621        self,
622        garnishes: &[Garnish<'a>],
623    ) -> GarnishedStatefulWidget<'a, Self> {
624        GarnishedStatefulWidget {
625            widget: self,
626            garnishes: garnishes.to_vec(),
627        }
628    }
629
630    /// Applies a garnish to the widget, wrapping it in a `DecoratedStatefulWidget`.
631    #[cfg(feature = "decorated_widget")]
632    fn decorate<R: RenderModifier>(self, garnish: R) -> DecoratedStatefulWidget<Self, R> {
633        DecoratedStatefulWidget::new(self, garnish)
634    }
635}
636
637// Blanket implementation for all widgets that implement `StatefulWidget`.
638impl<W: StatefulWidget> GarnishableStatefulWidget for W {}
639
640// RenderModifier implementations for ratatui `Style` & `Padding`
641
642impl RenderModifier for Style {
643    fn before_render(&self, area: Rect, buf: &mut Buffer) {
644        buf.set_style(area, *self);
645    }
646}
647
648#[cfg(test)]
649mod tests {
650    use super::*;
651    use ratatui::{
652        style::{Color, Style},
653        text::Line,
654    };
655
656    #[test]
657    fn garnish_chain() {
658        let widget = Line::raw("Test")
659            .garnish(Style::default().bg(Color::Blue))
660            .garnish(Padding::uniform(1));
661
662        assert_eq!(widget.len(), 2);
663        assert!(widget[0].is_style());
664        assert_eq!(widget.first_padding(), Some(&Padding::uniform(1)));
665    }
666
667    #[test]
668    fn widget_rendering() {
669        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
670        let widget = Line::raw("Test")
671            .garnish(Padding::uniform(1))
672            .garnish(Style::default().bg(Color::Blue));
673
674        widget.render(Rect::new(0, 0, 10, 10), &mut buffer);
675
676        // Check padding reduced the area
677        assert_eq!(buffer[(1, 1)].style().bg, Some(Color::Blue)); // Inside padded area
678        assert_eq!(buffer[(0, 0)].style().bg, Some(Color::Reset)); // Outside padded area
679    }
680}