Skip to main content

ratatui_widgets/
fill.rs

1//! The [`Fill`] widget paints every cell in its area with a single symbol and style.
2use alloc::borrow::Cow;
3
4use ratatui_core::buffer::Buffer;
5use ratatui_core::layout::Rect;
6use ratatui_core::style::{Style, Styled};
7use ratatui_core::widgets::Widget;
8
9/// A widget that fills its render area with a single repeated symbol and style.
10///
11/// [`Fill`] is a small building block for painting solid blocks of one symbol — backgrounds,
12/// separators, scrollbar tracks, custom borders, etc. — without writing the nested loop
13/// yourself. It composes naturally with the [`Stylize`] trait so the typical call site is
14/// a one-liner.
15///
16/// # Examples
17///
18/// ```
19/// use ratatui::layout::Rect;
20/// use ratatui::style::Stylize;
21/// use ratatui::widgets::{Fill, Widget};
22///
23/// # let mut buf = ratatui::buffer::Buffer::empty(Rect::new(0, 0, 10, 5));
24/// let fill = Fill::new("X").blue().bold();
25/// fill.render(Rect::new(0, 0, 10, 3), &mut buf);
26/// ```
27///
28/// This renders as:
29///
30/// ```plain
31/// XXXXXXXXXX
32/// XXXXXXXXXX
33/// XXXXXXXXXX
34/// ```
35///
36/// [`Fill`] accepts anything that converts into a [`Cow<str>`], so both string literals and
37/// owned [`String`](alloc::string::String)s work:
38///
39/// ```
40/// use ratatui::widgets::Fill;
41///
42/// let _ = Fill::new("•");
43/// let _ = Fill::new(String::from("•"));
44/// ```
45///
46/// Cells outside the buffer are silently clipped, mirroring the behavior of other widgets
47/// such as [`Clear`](crate::clear::Clear).
48///
49/// [`Stylize`]: ratatui_core::style::Stylize
50#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
51pub struct Fill<'a> {
52    symbol: Cow<'a, str>,
53    style: Style,
54}
55
56impl<'a> Fill<'a> {
57    /// Create a new [`Fill`] widget that paints `symbol` into every cell of its render area.
58    ///
59    /// The style defaults to [`Style::default`]; use the [`Stylize`] shorthands or
60    /// [`Fill::style`] to customize it.
61    ///
62    /// [`Stylize`]: ratatui_core::style::Stylize
63    pub fn new<S: Into<Cow<'a, str>>>(symbol: S) -> Self {
64        Self {
65            symbol: symbol.into(),
66            style: Style::default(),
67        }
68    }
69
70    /// Set the style used to paint each cell.
71    ///
72    /// `style` accepts any value convertible into a [`Style`] (e.g. [`Style`],
73    /// [`Color`](ratatui_core::style::Color), or your own type implementing
74    /// [`Into<Style>`]).
75    ///
76    /// This is a fluent setter method which must be chained or used as it consumes self
77    #[must_use = "method moves the value of self and returns the modified value"]
78    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
79        self.style = style.into();
80        self
81    }
82
83    /// Set the symbol painted into each cell.
84    ///
85    /// This is a fluent setter method which must be chained or used as it consumes self
86    #[must_use = "method moves the value of self and returns the modified value"]
87    pub fn symbol<S: Into<Cow<'a, str>>>(mut self, symbol: S) -> Self {
88        self.symbol = symbol.into();
89        self
90    }
91}
92
93impl Widget for Fill<'_> {
94    fn render(self, area: Rect, buf: &mut Buffer) {
95        Widget::render(&self, area, buf);
96    }
97}
98
99impl Widget for &Fill<'_> {
100    fn render(self, area: Rect, buf: &mut Buffer) {
101        let area = area.intersection(*buf.area());
102        if area.is_empty() {
103            return;
104        }
105        for position in area.positions() {
106            buf[position].set_symbol(&self.symbol).set_style(self.style);
107        }
108    }
109}
110
111impl Styled for Fill<'_> {
112    type Item = Self;
113
114    fn style(&self) -> Style {
115        self.style
116    }
117
118    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
119        self.style(style)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use alloc::string::String;
126
127    use ratatui_core::buffer::Buffer;
128    use ratatui_core::layout::Rect;
129    use ratatui_core::style::{Color, Style, Stylize};
130    use ratatui_core::widgets::Widget;
131
132    use super::*;
133
134    #[test]
135    #[rustfmt::skip]
136    fn fills_area_with_symbol_and_style() {
137        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 3));
138        Fill::new(".")
139            .style(Style::new().fg(Color::Red))
140            .render(Rect::new(1, 1, 3, 1), &mut buffer);
141
142        let mut expected = Buffer::with_lines([
143            "     ",
144            " ... ",
145            "     ",
146        ]);
147        for x in 1..=3 {
148            expected[(x, 1)].set_style(Style::new().fg(Color::Red));
149        }
150        assert_eq!(buffer, expected);
151    }
152
153    #[test]
154    fn clips_area_to_buffer() {
155        // Render area extends past the right and bottom edges of the buffer.
156        let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 2));
157        Fill::new("x").render(Rect::new(1, 1, 100, 100), &mut buffer);
158        assert_eq!(buffer, Buffer::with_lines(["   ", " xx"]));
159    }
160
161    #[test]
162    fn render_fully_out_of_bounds_is_noop() {
163        let mut buffer = Buffer::with_lines(["xxxxx"; 3]);
164        Fill::new(".").render(Rect::new(100, 100, 5, 5), &mut buffer);
165        assert_eq!(buffer, Buffer::with_lines(["xxxxx"; 3]));
166    }
167
168    #[test]
169    fn renders_with_offset_buffer_area() {
170        // Buffers can have a non-zero origin; ensure intersection logic still works.
171        let mut buffer = Buffer::empty(Rect::new(2, 2, 2, 2));
172        Fill::new("#").render(Rect::new(0, 0, 4, 4), &mut buffer);
173        let mut expected = Buffer::empty(Rect::new(2, 2, 2, 2));
174        for y in 2..4 {
175            for x in 2..4 {
176                expected[(x, y)].set_symbol("#");
177            }
178        }
179        assert_eq!(buffer, expected);
180    }
181
182    #[test]
183    fn stylize_shorthand_works() {
184        // The whole point of choosing the widget shape: composition with Stylize.
185        let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 1));
186        Fill::new("*")
187            .blue()
188            .bold()
189            .render(Rect::new(0, 0, 2, 1), &mut buffer);
190        let mut expected = Buffer::with_lines(["**"]);
191        for x in 0..2 {
192            expected[(x, 0)].set_style(Style::new().fg(Color::Blue).bold());
193        }
194        assert_eq!(buffer, expected);
195    }
196
197    #[test]
198    fn accepts_owned_string_symbol() {
199        let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 1));
200        Fill::new(String::from("•")).render(Rect::new(0, 0, 2, 1), &mut buffer);
201        assert_eq!(buffer, Buffer::with_lines(["••"]));
202    }
203
204    #[test]
205    fn symbol_setter_replaces_symbol() {
206        let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 1));
207        Fill::new("a")
208            .symbol("b")
209            .render(Rect::new(0, 0, 2, 1), &mut buffer);
210        assert_eq!(buffer, Buffer::with_lines(["bb"]));
211    }
212}