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}