ratatui/widgets/
block.rs

1//! Elements related to the `Block` base widget.
2//!
3//! This holds everything needed to display and configure a [`Block`].
4//!
5//! In its simplest form, a `Block` is a [border](Borders) around another widget. It can have a
6//! [title](Block::title) and [padding](Block::padding).
7
8use itertools::Itertools;
9use strum::{Display, EnumString};
10
11use crate::{
12    buffer::Buffer,
13    layout::{Alignment, Rect},
14    style::{Style, Styled},
15    symbols::border,
16    text::Line,
17    widgets::{Borders, Widget, WidgetRef},
18};
19
20mod padding;
21pub mod title;
22
23pub use padding::Padding;
24pub use title::{Position, Title};
25
26/// Base widget to be used to display a box border around all [upper level ones](crate::widgets).
27///
28/// The borders can be configured with [`Block::borders`] and others. A block can have multiple
29/// [`Title`] using [`Block::title`]. It can also be [styled](Block::style) and
30/// [padded](Block::padding).
31///
32/// You can call the title methods multiple times to add multiple titles. Each title will be
33/// rendered with a single space separating titles that are in the same position or alignment. When
34/// both centered and non-centered titles are rendered, the centered space is calculated based on
35/// the full width of the block, rather than the leftover width.
36///
37/// Titles are not rendered in the corners of the block unless there is no border on that edge. If
38/// the block is too small and multiple titles overlap, the border may get cut off at a corner.
39///
40/// ```plain
41/// ┌With at least a left border───
42///
43/// Without left border───
44/// ```
45/// # Constructor methods
46///
47/// - [`Block::new`] creates a new [`Block`] with no border or paddings.
48/// - [`Block::bordered`] Create a new block with all borders shown.
49///
50/// # Setter methods
51///
52/// These methods are fluent setters. They return a new [`Block`] with the specified property set.
53///
54/// - [`Block::borders`] Defines which borders to display.
55/// - [`Block::border_style`] Defines the style of the borders.
56/// - [`Block::border_type`] Sets the symbols used to display the border (e.g. single line, double
57///   line, thick or rounded borders).
58/// - [`Block::padding`] Defines the padding inside a [`Block`].
59/// - [`Block::style`] Sets the base style of the widget.
60/// - [`Block::title`] Adds a title to the block.
61/// - [`Block::title_alignment`] Sets the default [`Alignment`] for all block titles.
62/// - [`Block::title_style`] Applies the style to all titles.
63/// - [`Block::title_top`] Adds a title to the top of the block.
64/// - [`Block::title_bottom`] Adds a title to the bottom of the block.
65/// - [`Block::title_position`] Adds a title to the block.
66///
67/// # Other Methods
68/// - [`Block::inner`] Compute the inner area of a block based on its border visibility rules.
69///
70/// [`Style`]s are applied first to the entire block, then to the borders, and finally to the
71/// titles. If the block is used as a container for another widget, the inner widget can also be
72/// styled. See [`Style`] for more information on how merging styles works.
73///
74/// # Examples
75///
76/// ```
77/// use ratatui::{
78///     style::{Color, Style},
79///     widgets::{Block, BorderType, Borders},
80/// };
81///
82/// Block::new()
83///     .border_type(BorderType::Rounded)
84///     .borders(Borders::LEFT | Borders::RIGHT)
85///     .border_style(Style::default().fg(Color::White))
86///     .style(Style::default().bg(Color::Black))
87///     .title("Block");
88/// ```
89///
90/// You may also use multiple titles like in the following:
91/// ```
92/// use ratatui::widgets::{
93///     block::{Position, Title},
94///     Block,
95/// };
96///
97/// Block::new()
98///     .title("Title 1")
99///     .title(Title::from("Title 2").position(Position::Bottom));
100/// ```
101///
102/// You can also pass it as parameters of another widget so that the block surrounds them:
103/// ```
104/// use ratatui::widgets::{Block, Borders, List};
105///
106/// let surrounding_block = Block::default()
107///     .borders(Borders::ALL)
108///     .title("Here is a list of items");
109/// let items = ["Item 1", "Item 2", "Item 3"];
110/// let list = List::new(items).block(surrounding_block);
111/// ```
112#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
113pub struct Block<'a> {
114    /// List of titles
115    titles: Vec<(Option<Position>, Line<'a>)>,
116    /// The style to be patched to all titles of the block
117    titles_style: Style,
118    /// The default alignment of the titles that don't have one
119    titles_alignment: Alignment,
120    /// The default position of the titles that don't have one
121    titles_position: Position,
122    /// Visible borders
123    borders: Borders,
124    /// Border style
125    border_style: Style,
126    /// The symbols used to render the border. The default is plain lines but one can choose to
127    /// have rounded or doubled lines instead or a custom set of symbols
128    border_set: border::Set,
129    /// Widget style
130    style: Style,
131    /// Block padding
132    padding: Padding,
133}
134
135/// The type of border of a [`Block`].
136///
137/// See the [`borders`](Block::borders) method of `Block` to configure its borders.
138#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
139pub enum BorderType {
140    /// A plain, simple border.
141    ///
142    /// This is the default
143    ///
144    /// # Example
145    ///
146    /// ```plain
147    /// ┌───────┐
148    /// │       │
149    /// └───────┘
150    /// ```
151    #[default]
152    Plain,
153    /// A plain border with rounded corners.
154    ///
155    /// # Example
156    ///
157    /// ```plain
158    /// ╭───────╮
159    /// │       │
160    /// ╰───────╯
161    /// ```
162    Rounded,
163    /// A doubled border.
164    ///
165    /// Note this uses one character that draws two lines.
166    ///
167    /// # Example
168    ///
169    /// ```plain
170    /// ╔═══════╗
171    /// ║       ║
172    /// ╚═══════╝
173    /// ```
174    Double,
175    /// A thick border.
176    ///
177    /// # Example
178    ///
179    /// ```plain
180    /// ┏━━━━━━━┓
181    /// ┃       ┃
182    /// ┗━━━━━━━┛
183    /// ```
184    Thick,
185    /// A border with a single line on the inside of a half block.
186    ///
187    /// # Example
188    ///
189    /// ```plain
190    /// ▗▄▄▄▄▄▄▄▖
191    /// ▐       ▌
192    /// ▐       ▌
193    /// ▝▀▀▀▀▀▀▀▘
194    QuadrantInside,
195
196    /// A border with a single line on the outside of a half block.
197    ///
198    /// # Example
199    ///
200    /// ```plain
201    /// ▛▀▀▀▀▀▀▀▜
202    /// ▌       ▐
203    /// ▌       ▐
204    /// ▙▄▄▄▄▄▄▄▟
205    QuadrantOutside,
206}
207
208impl<'a> Block<'a> {
209    /// Creates a new block with no [`Borders`] or [`Padding`].
210    pub const fn new() -> Self {
211        Self {
212            titles: Vec::new(),
213            titles_style: Style::new(),
214            titles_alignment: Alignment::Left,
215            titles_position: Position::Top,
216            borders: Borders::NONE,
217            border_style: Style::new(),
218            border_set: BorderType::Plain.to_border_set(),
219            style: Style::new(),
220            padding: Padding::ZERO,
221        }
222    }
223
224    /// Create a new block with [all borders](Borders::ALL) shown
225    ///
226    /// ```
227    /// use ratatui::widgets::{Block, Borders};
228    ///
229    /// assert_eq!(Block::bordered(), Block::new().borders(Borders::ALL));
230    /// ```
231    pub const fn bordered() -> Self {
232        let mut block = Self::new();
233        block.borders = Borders::ALL;
234        block
235    }
236
237    /// Adds a title to the block.
238    ///
239    /// The `title` function allows you to add a title to the block. You can call this function
240    /// multiple times to add multiple titles.
241    ///
242    /// Each title will be rendered with a single space separating titles that are in the same
243    /// position or alignment. When both centered and non-centered titles are rendered, the centered
244    /// space is calculated based on the full width of the block, rather than the leftover width.
245    ///
246    /// You can provide any type that can be converted into [`Title`] including: strings, string
247    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](crate::text::Span), or vectors of
248    /// [spans](crate::text::Span) (`Vec<Span>`).
249    ///
250    /// By default, the titles will avoid being rendered in the corners of the block but will align
251    /// against the left or right edge of the block if there is no border on that edge. The
252    /// following demonstrates this behavior, notice the second title is one character off to the
253    /// left.
254    ///
255    /// ```plain
256    /// ┌With at least a left border───
257    ///
258    /// Without left border───
259    /// ```
260    ///
261    /// Note: If the block is too small and multiple titles overlap, the border might get cut off at
262    /// a corner.
263    ///
264    /// # Examples
265    ///
266    /// See the [Block example] for a visual representation of how the various borders and styles
267    /// look when rendered.
268    ///
269    /// The following example demonstrates:
270    /// - Default title alignment
271    /// - Multiple titles (notice "Center" is centered according to the full with of the block, not
272    ///   the leftover space)
273    /// - Two titles with the same alignment (notice the left titles are separated)
274    /// ```
275    /// use ratatui::{
276    ///     text::Line,
277    ///     widgets::{Block, Borders},
278    /// };
279    ///
280    /// Block::new()
281    ///     .title("Title") // By default in the top left corner
282    ///     .title(Line::from("Left").left_aligned()) // also on the left
283    ///     .title(Line::from("Right").right_aligned())
284    ///     .title(Line::from("Center").centered());
285    /// // Renders
286    /// // ┌Title─Left────Center─────────Right┐
287    /// ```
288    ///
289    /// # See also
290    ///
291    /// Titles attached to a block can have default behaviors. See
292    /// - [`Block::title_style`]
293    /// - [`Block::title_alignment`]
294    /// - [`Block::title_position`]
295    ///
296    /// # Future improvements
297    ///
298    /// In a future release of Ratatui this method will be changed to accept `Into<Line>` instead of
299    /// `Into<Title>`. This allows us to remove the unnecessary `Title` struct and store the
300    /// position in the block itself. For more information see
301    /// <https://github.com/ratatui/ratatui/issues/738>.
302    ///
303    /// [Block example]: https://github.com/ratatui/ratatui/blob/main/examples/README.md#block
304    #[must_use = "method moves the value of self and returns the modified value"]
305    pub fn title<T>(mut self, title: T) -> Self
306    where
307        T: Into<Title<'a>>,
308    {
309        let title = title.into();
310        let position = title.position;
311        let mut content = title.content;
312        if let Some(alignment) = title.alignment {
313            content = content.alignment(alignment);
314        }
315        self.titles.push((position, content));
316        self
317    }
318
319    /// Adds a title to the top of the block.
320    ///
321    /// You can provide any type that can be converted into [`Line`] including: strings, string
322    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](crate::text::Span), or vectors of
323    /// [spans](crate::text::Span) (`Vec<Span>`).
324    ///
325    /// # Example
326    ///
327    /// ```
328    /// use ratatui::{ widgets::Block, text::Line };
329    ///
330    /// Block::bordered()
331    ///     .title_top("Left1") // By default in the top left corner
332    ///     .title_top(Line::from("Left2").left_aligned())
333    ///     .title_top(Line::from("Right").right_aligned())
334    ///     .title_top(Line::from("Center").centered());
335    ///
336    /// // Renders
337    /// // ┌Left1─Left2───Center─────────Right┐
338    /// // │                                  │
339    /// // └──────────────────────────────────┘
340    /// ```
341    #[must_use = "method moves the value of self and returns the modified value"]
342    pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
343        let line = title.into();
344        self.titles.push((Some(Position::Top), line));
345        self
346    }
347
348    /// Adds a title to the bottom of the block.
349    ///
350    /// You can provide any type that can be converted into [`Line`] including: strings, string
351    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](crate::text::Span), or vectors of
352    /// [spans](crate::text::Span) (`Vec<Span>`).
353    ///
354    /// # Example
355    ///
356    /// ```
357    /// use ratatui::{ widgets::Block, text::Line };
358    ///
359    /// Block::bordered()
360    ///     .title_bottom("Left1") // By default in the top left corner
361    ///     .title_bottom(Line::from("Left2").left_aligned())
362    ///     .title_bottom(Line::from("Right").right_aligned())
363    ///     .title_bottom(Line::from("Center").centered());
364    ///
365    /// // Renders
366    /// // ┌──────────────────────────────────┐
367    /// // │                                  │
368    /// // └Left1─Left2───Center─────────Right┘
369    /// ```
370    #[must_use = "method moves the value of self and returns the modified value"]
371    pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
372        let line = title.into();
373        self.titles.push((Some(Position::Bottom), line));
374        self
375    }
376
377    /// Applies the style to all titles.
378    ///
379    /// This style will be applied to all titles of the block. If a title has a style set, it will
380    /// be applied after this style. This style will be applied after any [`Block::style`] or
381    /// [`Block::border_style`] is applied.
382    ///
383    /// See [`Style`] for more information on how merging styles works.
384    ///
385    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
386    /// your own type that implements [`Into<Style>`]).
387    ///
388    /// [`Color`]: crate::style::Color
389    #[must_use = "method moves the value of self and returns the modified value"]
390    pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
391        self.titles_style = style.into();
392        self
393    }
394
395    /// Sets the default [`Alignment`] for all block titles.
396    ///
397    /// Titles that explicitly set an [`Alignment`] will ignore this.
398    ///
399    /// # Example
400    ///
401    /// This example aligns all titles in the center except the "right" title which explicitly sets
402    /// [`Alignment::Right`].
403    /// ```
404    /// use ratatui::{layout::Alignment, text::Line, widgets::Block};
405    ///
406    /// Block::new()
407    ///     .title_alignment(Alignment::Center)
408    ///     // This title won't be aligned in the center
409    ///     .title(Line::from("right").right_aligned())
410    ///     .title("foo")
411    ///     .title("bar");
412    /// ```
413    #[must_use = "method moves the value of self and returns the modified value"]
414    pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
415        self.titles_alignment = alignment;
416        self
417    }
418
419    /// Sets the default [`Position`] for all block [titles](Title).
420    ///
421    /// Titles that explicitly set a [`Position`] will ignore this.
422    ///
423    /// # Example
424    ///
425    /// This example positions all titles on the bottom except the "top" title which explicitly sets
426    /// [`Position::Top`].
427    /// ```
428    /// use ratatui::widgets::{block::Position, Block};
429    ///
430    /// Block::new()
431    ///     .title_position(Position::Bottom)
432    ///     // This title won't be aligned in the center
433    ///     .title_top("top")
434    ///     .title("foo")
435    ///     .title("bar");
436    /// ```
437    #[must_use = "method moves the value of self and returns the modified value"]
438    pub const fn title_position(mut self, position: Position) -> Self {
439        self.titles_position = position;
440        self
441    }
442
443    /// Defines the style of the borders.
444    ///
445    /// This style is applied only to the areas covered by borders, and is applied to the block
446    /// after any [`Block::style`] is applied.
447    ///
448    /// See [`Style`] for more information on how merging styles works.
449    ///
450    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
451    /// your own type that implements [`Into<Style>`]).
452    ///
453    /// # Example
454    ///
455    /// This example shows a `Block` with blue borders.
456    /// ```
457    /// use ratatui::{
458    ///     style::{Style, Stylize},
459    ///     widgets::Block,
460    /// };
461    /// Block::bordered().border_style(Style::new().blue());
462    /// ```
463    ///
464    /// [`Color`]: crate::style::Color
465    #[must_use = "method moves the value of self and returns the modified value"]
466    pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
467        self.border_style = style.into();
468        self
469    }
470
471    /// Defines the style of the entire block.
472    ///
473    /// This is the most generic [`Style`] a block can receive, it will be merged with any other
474    /// more specific styles. Elements can be styled further with [`Block::title_style`] and
475    /// [`Block::border_style`], which will be applied on top of this style. If the block is used as
476    /// a container for another widget (e.g. a [`Paragraph`]), then the style of the widget is
477    /// generally applied before this style.
478    ///
479    /// See [`Style`] for more information on how merging styles works.
480    ///
481    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
482    /// your own type that implements [`Into<Style>`]).
483    ///
484    /// # Example
485    ///
486    /// ```
487    /// use ratatui::{
488    ///     style::{Color, Style, Stylize},
489    ///     widgets::{Block, Paragraph},
490    /// };
491    ///
492    /// let block = Block::new().style(Style::new().red().on_black());
493    ///
494    /// // For border and title you can additionally apply styles on top of the block level style.
495    /// let block = Block::new()
496    ///     .style(Style::new().red().bold().italic())
497    ///     .border_style(Style::new().not_italic()) // will be red and bold
498    ///     .title_style(Style::new().not_bold()) // will be red and italic
499    ///     .title("Title");
500    ///
501    /// // To style the inner widget, you can style the widget itself.
502    /// let paragraph = Paragraph::new("Content")
503    ///     .block(block)
504    ///     .style(Style::new().white().not_bold()); // will be white, and italic
505    /// ```
506    ///
507    /// [`Paragraph`]: crate::widgets::Paragraph
508    /// [`Color`]: crate::style::Color
509    #[must_use = "method moves the value of self and returns the modified value"]
510    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
511        self.style = style.into();
512        self
513    }
514
515    /// Defines which borders to display.
516    ///
517    /// [`Borders`] can also be styled with [`Block::border_style`] and [`Block::border_type`].
518    ///
519    /// # Examples
520    ///
521    /// Display left and right borders.
522    /// ```
523    /// use ratatui::widgets::{Block, Borders};
524    /// Block::new().borders(Borders::LEFT | Borders::RIGHT);
525    /// ```
526    ///
527    /// To show all borders you can abbreviate this with [`Block::bordered`]
528    #[must_use = "method moves the value of self and returns the modified value"]
529    pub const fn borders(mut self, flag: Borders) -> Self {
530        self.borders = flag;
531        self
532    }
533
534    /// Sets the symbols used to display the border (e.g. single line, double line, thick or
535    /// rounded borders).
536    ///
537    /// Setting this overwrites any custom [`border_set`](Block::border_set) that was set.
538    ///
539    /// See [`BorderType`] for the full list of available symbols.
540    ///
541    /// # Examples
542    ///
543    /// ```
544    /// use ratatui::widgets::{Block, BorderType};
545    /// Block::bordered()
546    ///     .border_type(BorderType::Rounded)
547    ///     .title("Block");
548    /// // Renders
549    /// // ╭Block╮
550    /// // │     │
551    /// // ╰─────╯
552    /// ```
553    #[must_use = "method moves the value of self and returns the modified value"]
554    pub const fn border_type(mut self, border_type: BorderType) -> Self {
555        self.border_set = border_type.to_border_set();
556        self
557    }
558
559    /// Sets the symbols used to display the border as a [`crate::symbols::border::Set`].
560    ///
561    /// Setting this overwrites any [`border_type`](Block::border_type) that was set.
562    ///
563    /// # Examples
564    ///
565    /// ```
566    /// use ratatui::{widgets::Block, symbols};
567    ///
568    /// Block::bordered().border_set(symbols::border::DOUBLE).title("Block");
569    /// // Renders
570    /// // ╔Block╗
571    /// // ║     ║
572    /// // ╚═════╝
573    #[must_use = "method moves the value of self and returns the modified value"]
574    pub const fn border_set(mut self, border_set: border::Set) -> Self {
575        self.border_set = border_set;
576        self
577    }
578
579    /// Defines the padding inside a `Block`.
580    ///
581    /// See [`Padding`] for more information.
582    ///
583    /// # Examples
584    ///
585    /// This renders a `Block` with no padding (the default).
586    /// ```
587    /// use ratatui::widgets::{Block, Padding};
588    ///
589    /// Block::bordered().padding(Padding::ZERO);
590    /// // Renders
591    /// // ┌───────┐
592    /// // │content│
593    /// // └───────┘
594    /// ```
595    ///
596    /// This example shows a `Block` with padding left and right ([`Padding::horizontal`]).
597    /// Notice the two spaces before and after the content.
598    /// ```
599    /// use ratatui::widgets::{Block, Padding};
600    ///
601    /// Block::bordered().padding(Padding::horizontal(2));
602    /// // Renders
603    /// // ┌───────────┐
604    /// // │  content  │
605    /// // └───────────┘
606    /// ```
607    #[must_use = "method moves the value of self and returns the modified value"]
608    pub const fn padding(mut self, padding: Padding) -> Self {
609        self.padding = padding;
610        self
611    }
612
613    /// Compute the inner area of a block based on its border visibility rules.
614    ///
615    /// # Examples
616    ///
617    /// Draw a block nested within another block
618    /// ```
619    /// use ratatui::{widgets::Block, Frame};
620    ///
621    /// # fn render_nested_block(frame: &mut Frame) {
622    /// let outer_block = Block::bordered().title("Outer");
623    /// let inner_block = Block::bordered().title("Inner");
624    ///
625    /// let outer_area = frame.area();
626    /// let inner_area = outer_block.inner(outer_area);
627    ///
628    /// frame.render_widget(outer_block, outer_area);
629    /// frame.render_widget(inner_block, inner_area);
630    /// # }
631    /// // Renders
632    /// // ┌Outer────────┐
633    /// // │┌Inner──────┐│
634    /// // ││           ││
635    /// // │└───────────┘│
636    /// // └─────────────┘
637    /// ```
638    pub fn inner(&self, area: Rect) -> Rect {
639        let mut inner = area;
640        if self.borders.intersects(Borders::LEFT) {
641            inner.x = inner.x.saturating_add(1).min(inner.right());
642            inner.width = inner.width.saturating_sub(1);
643        }
644        if self.borders.intersects(Borders::TOP) || self.has_title_at_position(Position::Top) {
645            inner.y = inner.y.saturating_add(1).min(inner.bottom());
646            inner.height = inner.height.saturating_sub(1);
647        }
648        if self.borders.intersects(Borders::RIGHT) {
649            inner.width = inner.width.saturating_sub(1);
650        }
651        if self.borders.intersects(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom)
652        {
653            inner.height = inner.height.saturating_sub(1);
654        }
655
656        inner.x = inner.x.saturating_add(self.padding.left);
657        inner.y = inner.y.saturating_add(self.padding.top);
658
659        inner.width = inner
660            .width
661            .saturating_sub(self.padding.left + self.padding.right);
662        inner.height = inner
663            .height
664            .saturating_sub(self.padding.top + self.padding.bottom);
665
666        inner
667    }
668
669    fn has_title_at_position(&self, position: Position) -> bool {
670        self.titles
671            .iter()
672            .any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
673    }
674}
675
676impl BorderType {
677    /// Convert this `BorderType` into the corresponding [`Set`](border::Set) of border symbols.
678    pub const fn border_symbols(border_type: Self) -> border::Set {
679        match border_type {
680            Self::Plain => border::PLAIN,
681            Self::Rounded => border::ROUNDED,
682            Self::Double => border::DOUBLE,
683            Self::Thick => border::THICK,
684            Self::QuadrantInside => border::QUADRANT_INSIDE,
685            Self::QuadrantOutside => border::QUADRANT_OUTSIDE,
686        }
687    }
688
689    /// Convert this `BorderType` into the corresponding [`Set`](border::Set) of border symbols.
690    pub const fn to_border_set(self) -> border::Set {
691        Self::border_symbols(self)
692    }
693}
694
695impl Widget for Block<'_> {
696    fn render(self, area: Rect, buf: &mut Buffer) {
697        self.render_ref(area, buf);
698    }
699}
700
701impl WidgetRef for Block<'_> {
702    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
703        let area = area.intersection(buf.area);
704        if area.is_empty() {
705            return;
706        }
707        buf.set_style(area, self.style);
708        self.render_borders(area, buf);
709        self.render_titles(area, buf);
710    }
711}
712
713impl Block<'_> {
714    fn render_borders(&self, area: Rect, buf: &mut Buffer) {
715        self.render_left_side(area, buf);
716        self.render_top_side(area, buf);
717        self.render_right_side(area, buf);
718        self.render_bottom_side(area, buf);
719
720        self.render_bottom_right_corner(buf, area);
721        self.render_top_right_corner(buf, area);
722        self.render_bottom_left_corner(buf, area);
723        self.render_top_left_corner(buf, area);
724    }
725
726    fn render_titles(&self, area: Rect, buf: &mut Buffer) {
727        self.render_title_position(Position::Top, area, buf);
728        self.render_title_position(Position::Bottom, area, buf);
729    }
730
731    fn render_title_position(&self, position: Position, area: Rect, buf: &mut Buffer) {
732        // NOTE: the order in which these functions are called defines the overlapping behavior
733        self.render_right_titles(position, area, buf);
734        self.render_center_titles(position, area, buf);
735        self.render_left_titles(position, area, buf);
736    }
737
738    fn render_left_side(&self, area: Rect, buf: &mut Buffer) {
739        if self.borders.contains(Borders::LEFT) {
740            for y in area.top()..area.bottom() {
741                buf[(area.left(), y)]
742                    .set_symbol(self.border_set.vertical_left)
743                    .set_style(self.border_style);
744            }
745        }
746    }
747
748    fn render_top_side(&self, area: Rect, buf: &mut Buffer) {
749        if self.borders.contains(Borders::TOP) {
750            for x in area.left()..area.right() {
751                buf[(x, area.top())]
752                    .set_symbol(self.border_set.horizontal_top)
753                    .set_style(self.border_style);
754            }
755        }
756    }
757
758    fn render_right_side(&self, area: Rect, buf: &mut Buffer) {
759        if self.borders.contains(Borders::RIGHT) {
760            let x = area.right() - 1;
761            for y in area.top()..area.bottom() {
762                buf[(x, y)]
763                    .set_symbol(self.border_set.vertical_right)
764                    .set_style(self.border_style);
765            }
766        }
767    }
768
769    fn render_bottom_side(&self, area: Rect, buf: &mut Buffer) {
770        if self.borders.contains(Borders::BOTTOM) {
771            let y = area.bottom() - 1;
772            for x in area.left()..area.right() {
773                buf[(x, y)]
774                    .set_symbol(self.border_set.horizontal_bottom)
775                    .set_style(self.border_style);
776            }
777        }
778    }
779
780    fn render_bottom_right_corner(&self, buf: &mut Buffer, area: Rect) {
781        if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
782            buf[(area.right() - 1, area.bottom() - 1)]
783                .set_symbol(self.border_set.bottom_right)
784                .set_style(self.border_style);
785        }
786    }
787
788    fn render_top_right_corner(&self, buf: &mut Buffer, area: Rect) {
789        if self.borders.contains(Borders::RIGHT | Borders::TOP) {
790            buf[(area.right() - 1, area.top())]
791                .set_symbol(self.border_set.top_right)
792                .set_style(self.border_style);
793        }
794    }
795
796    fn render_bottom_left_corner(&self, buf: &mut Buffer, area: Rect) {
797        if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
798            buf[(area.left(), area.bottom() - 1)]
799                .set_symbol(self.border_set.bottom_left)
800                .set_style(self.border_style);
801        }
802    }
803
804    fn render_top_left_corner(&self, buf: &mut Buffer, area: Rect) {
805        if self.borders.contains(Borders::LEFT | Borders::TOP) {
806            buf[(area.left(), area.top())]
807                .set_symbol(self.border_set.top_left)
808                .set_style(self.border_style);
809        }
810    }
811
812    /// Render titles aligned to the right of the block
813    ///
814    /// Currently (due to the way lines are truncated), the right side of the leftmost title will
815    /// be cut off if the block is too small to fit all titles. This is not ideal and should be
816    /// the left side of that leftmost that is cut off. This is due to the line being truncated
817    /// incorrectly. See <https://github.com/ratatui/ratatui/issues/932>
818    #[allow(clippy::similar_names)]
819    fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
820        let titles = self.filtered_titles(position, Alignment::Right);
821        let mut titles_area = self.titles_area(area, position);
822
823        // render titles in reverse order to align them to the right
824        for title in titles.rev() {
825            if titles_area.is_empty() {
826                break;
827            }
828            let title_width = title.width() as u16;
829            let title_area = Rect {
830                x: titles_area
831                    .right()
832                    .saturating_sub(title_width)
833                    .max(titles_area.left()),
834                width: title_width.min(titles_area.width),
835                ..titles_area
836            };
837            buf.set_style(title_area, self.titles_style);
838            title.render_ref(title_area, buf);
839
840            // bump the width of the titles area to the left
841            titles_area.width = titles_area
842                .width
843                .saturating_sub(title_width)
844                .saturating_sub(1); // space between titles
845        }
846    }
847
848    /// Render titles in the center of the block
849    ///
850    /// Currently this method aligns the titles to the left inside a centered area. This is not
851    /// ideal and should be fixed in the future to align the titles to the center of the block and
852    /// truncate both sides of the titles if the block is too small to fit all titles.
853    #[allow(clippy::similar_names)]
854    fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
855        let titles = self
856            .filtered_titles(position, Alignment::Center)
857            .collect_vec();
858        let total_width = titles
859            .iter()
860            .map(|title| title.width() as u16 + 1) // space between titles
861            .sum::<u16>()
862            .saturating_sub(1); // no space for the last title
863
864        let titles_area = self.titles_area(area, position);
865        let mut titles_area = Rect {
866            x: titles_area.left() + (titles_area.width.saturating_sub(total_width) / 2),
867            ..titles_area
868        };
869        for title in titles {
870            if titles_area.is_empty() {
871                break;
872            }
873            let title_width = title.width() as u16;
874            let title_area = Rect {
875                width: title_width.min(titles_area.width),
876                ..titles_area
877            };
878            buf.set_style(title_area, self.titles_style);
879            title.render_ref(title_area, buf);
880
881            // bump the titles area to the right and reduce its width
882            titles_area.x = titles_area.x.saturating_add(title_width + 1);
883            titles_area.width = titles_area.width.saturating_sub(title_width + 1);
884        }
885    }
886
887    /// Render titles aligned to the left of the block
888    #[allow(clippy::similar_names)]
889    fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
890        let titles = self.filtered_titles(position, Alignment::Left);
891        let mut titles_area = self.titles_area(area, position);
892        for title in titles {
893            if titles_area.is_empty() {
894                break;
895            }
896            let title_width = title.width() as u16;
897            let title_area = Rect {
898                width: title_width.min(titles_area.width),
899                ..titles_area
900            };
901            buf.set_style(title_area, self.titles_style);
902            title.render_ref(title_area, buf);
903
904            // bump the titles area to the right and reduce its width
905            titles_area.x = titles_area.x.saturating_add(title_width + 1);
906            titles_area.width = titles_area.width.saturating_sub(title_width + 1);
907        }
908    }
909
910    /// An iterator over the titles that match the position and alignment
911    fn filtered_titles(
912        &self,
913        position: Position,
914        alignment: Alignment,
915    ) -> impl DoubleEndedIterator<Item = &Line> {
916        self.titles
917            .iter()
918            .filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
919            .filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
920            .map(|(_, line)| line)
921    }
922
923    /// An area that is one line tall and spans the width of the block excluding the borders and
924    /// is positioned at the top or bottom of the block.
925    fn titles_area(&self, area: Rect, position: Position) -> Rect {
926        let left_border = u16::from(self.borders.contains(Borders::LEFT));
927        let right_border = u16::from(self.borders.contains(Borders::RIGHT));
928        Rect {
929            x: area.left() + left_border,
930            y: match position {
931                Position::Top => area.top(),
932                Position::Bottom => area.bottom() - 1,
933            },
934            width: area
935                .width
936                .saturating_sub(left_border)
937                .saturating_sub(right_border),
938            height: 1,
939        }
940    }
941
942    /// Calculate the left, and right space the [`Block`] will take up.
943    ///
944    /// The result takes the [`Block`]'s, [`Borders`], and [`Padding`] into account.
945    pub(crate) fn horizontal_space(&self) -> (u16, u16) {
946        let left = self
947            .padding
948            .left
949            .saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
950        let right = self
951            .padding
952            .right
953            .saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
954        (left, right)
955    }
956
957    /// Calculate the top, and bottom space that the [`Block`] will take up.
958    ///
959    /// Takes the [`Padding`], [`Title`]'s position, and the [`Borders`] that are selected into
960    /// account when calculating the result.
961    pub(crate) fn vertical_space(&self) -> (u16, u16) {
962        let has_top =
963            self.borders.contains(Borders::TOP) || self.has_title_at_position(Position::Top);
964        let top = self.padding.top + u16::from(has_top);
965        let has_bottom =
966            self.borders.contains(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom);
967        let bottom = self.padding.bottom + u16::from(has_bottom);
968        (top, bottom)
969    }
970}
971
972/// An extension trait for [`Block`] that provides some convenience methods.
973///
974/// This is implemented for [`Option<Block>`](Option) to simplify the common case of having a
975/// widget with an optional block.
976pub trait BlockExt {
977    /// Return the inner area of the block if it is `Some`. Otherwise, returns `area`.
978    ///
979    /// This is a useful convenience method for widgets that have an `Option<Block>` field
980    fn inner_if_some(&self, area: Rect) -> Rect;
981}
982
983impl BlockExt for Option<Block<'_>> {
984    fn inner_if_some(&self, area: Rect) -> Rect {
985        self.as_ref().map_or(area, |block| block.inner(area))
986    }
987}
988
989impl<'a> Styled for Block<'a> {
990    type Item = Self;
991
992    fn style(&self) -> Style {
993        self.style
994    }
995
996    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
997        self.style(style)
998    }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003    use rstest::rstest;
1004    use strum::ParseError;
1005
1006    use super::*;
1007    use crate::style::{Color, Modifier, Stylize};
1008
1009    #[test]
1010    fn create_with_all_borders() {
1011        let block = Block::bordered();
1012        assert_eq!(block.borders, Borders::all());
1013    }
1014
1015    #[rstest]
1016    #[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
1017    #[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
1018    #[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
1019    #[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1020    #[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
1021    #[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
1022    #[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
1023    #[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1024    #[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
1025    #[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
1026    #[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
1027    #[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1028    #[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
1029    #[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
1030    #[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
1031    #[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1032    #[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
1033    #[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
1034    #[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
1035    #[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
1036    #[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
1037    #[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
1038    fn inner_takes_into_account_the_borders(
1039        #[case] borders: Borders,
1040        #[case] area: Rect,
1041        #[case] expected: Rect,
1042    ) {
1043        let block = Block::new().borders(borders);
1044        assert_eq!(block.inner(area), expected);
1045    }
1046
1047    #[rstest]
1048    #[case::left(Alignment::Left)]
1049    #[case::center(Alignment::Center)]
1050    #[case::right(Alignment::Right)]
1051    fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
1052        let area = Rect::new(0, 0, 0, 1);
1053        let expected = Rect::new(0, 1, 0, 0);
1054
1055        let block = Block::new().title(Line::from("Test").alignment(alignment));
1056        assert_eq!(block.inner(area), expected);
1057    }
1058
1059    #[rstest]
1060    #[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
1061    #[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
1062    #[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
1063    #[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
1064    fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
1065        let area = Rect::new(0, 0, 0, 2);
1066        assert_eq!(block.inner(area), expected);
1067    }
1068
1069    #[test]
1070    fn has_title_at_position_takes_into_account_all_positioning_declarations() {
1071        let block = Block::new();
1072        assert!(!block.has_title_at_position(Position::Top));
1073        assert!(!block.has_title_at_position(Position::Bottom));
1074
1075        let block = Block::new().title_top("test");
1076        assert!(block.has_title_at_position(Position::Top));
1077        assert!(!block.has_title_at_position(Position::Bottom));
1078
1079        let block = Block::new().title_bottom("test");
1080        assert!(!block.has_title_at_position(Position::Top));
1081        assert!(block.has_title_at_position(Position::Bottom));
1082
1083        #[allow(deprecated)] // until Title is removed
1084        let block = Block::new()
1085            .title(Title::from("Test").position(Position::Top))
1086            .title_position(Position::Bottom);
1087        assert!(block.has_title_at_position(Position::Top));
1088        assert!(!block.has_title_at_position(Position::Bottom));
1089
1090        #[allow(deprecated)] // until Title is removed
1091        let block = Block::new()
1092            .title(Title::from("Test").position(Position::Bottom))
1093            .title_position(Position::Top);
1094        assert!(!block.has_title_at_position(Position::Top));
1095        assert!(block.has_title_at_position(Position::Bottom));
1096
1097        let block = Block::new().title_top("test").title_bottom("test");
1098        assert!(block.has_title_at_position(Position::Top));
1099        assert!(block.has_title_at_position(Position::Bottom));
1100
1101        #[allow(deprecated)] // until Title is removed
1102        let block = Block::new()
1103            .title(Title::from("Test").position(Position::Top))
1104            .title(Title::from("Test"))
1105            .title_position(Position::Bottom);
1106        assert!(block.has_title_at_position(Position::Top));
1107        assert!(block.has_title_at_position(Position::Bottom));
1108
1109        #[allow(deprecated)] // until Title is removed
1110        let block = Block::new()
1111            .title(Title::from("Test"))
1112            .title(Title::from("Test").position(Position::Bottom))
1113            .title_position(Position::Top);
1114        assert!(block.has_title_at_position(Position::Top));
1115        assert!(block.has_title_at_position(Position::Bottom));
1116    }
1117
1118    #[rstest]
1119    #[case::none(Borders::NONE, (0, 0))]
1120    #[case::top(Borders::TOP, (1, 0))]
1121    #[case::right(Borders::RIGHT, (0, 0))]
1122    #[case::bottom(Borders::BOTTOM, (0, 1))]
1123    #[case::left(Borders::LEFT, (0, 0))]
1124    #[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
1125    #[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
1126    #[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
1127    #[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
1128    #[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
1129    #[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
1130    fn vertical_space_takes_into_account_borders(
1131        #[case] borders: Borders,
1132        #[case] vertical_space: (u16, u16),
1133    ) {
1134        let block = Block::new().borders(borders);
1135        assert_eq!(block.vertical_space(), vertical_space);
1136    }
1137
1138    #[rstest]
1139    #[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
1140    #[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
1141    #[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
1142    #[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
1143    #[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
1144    #[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
1145    #[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
1146    fn vertical_space_takes_into_account_padding(
1147        #[case] borders: Borders,
1148        #[case] padding: Padding,
1149        #[case] vertical_space: (u16, u16),
1150    ) {
1151        let block = Block::new().borders(borders).padding(padding);
1152        assert_eq!(block.vertical_space(), vertical_space);
1153    }
1154
1155    #[test]
1156    fn vertical_space_takes_into_account_titles() {
1157        let block = Block::new().title_top("Test");
1158        assert_eq!(block.vertical_space(), (1, 0));
1159
1160        let block = Block::new().title_bottom("Test");
1161        assert_eq!(block.vertical_space(), (0, 1));
1162    }
1163
1164    #[rstest]
1165    #[case::top_border_top_title(Block::new(), Borders::TOP, Position::Top, (1, 0))]
1166    #[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Top, (1, 0))]
1167    #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Top, (1, 1))]
1168    #[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Top, (1, 0))]
1169    #[case::top_border_top_title(Block::new(), Borders::TOP, Position::Bottom, (1, 1))]
1170    #[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Bottom, (0, 1))]
1171    #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Bottom, (0, 1))]
1172    #[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Bottom, (0, 1))]
1173    fn vertical_space_takes_into_account_borders_and_title(
1174        #[case] block: Block,
1175        #[case] borders: Borders,
1176        #[case] pos: Position,
1177        #[case] vertical_space: (u16, u16),
1178    ) {
1179        let block = block.borders(borders).title_position(pos).title("Test");
1180        assert_eq!(block.vertical_space(), vertical_space);
1181    }
1182
1183    #[test]
1184    fn horizontal_space_takes_into_account_borders() {
1185        let block = Block::bordered();
1186        assert_eq!(block.horizontal_space(), (1, 1));
1187
1188        let block = Block::new().borders(Borders::LEFT);
1189        assert_eq!(block.horizontal_space(), (1, 0));
1190
1191        let block = Block::new().borders(Borders::RIGHT);
1192        assert_eq!(block.horizontal_space(), (0, 1));
1193    }
1194
1195    #[test]
1196    fn horizontal_space_takes_into_account_padding() {
1197        let block = Block::new().padding(Padding::new(1, 1, 100, 100));
1198        assert_eq!(block.horizontal_space(), (1, 1));
1199
1200        let block = Block::new().padding(Padding::new(3, 5, 0, 0));
1201        assert_eq!(block.horizontal_space(), (3, 5));
1202
1203        let block = Block::new().padding(Padding::new(0, 1, 100, 100));
1204        assert_eq!(block.horizontal_space(), (0, 1));
1205
1206        let block = Block::new().padding(Padding::new(1, 0, 100, 100));
1207        assert_eq!(block.horizontal_space(), (1, 0));
1208    }
1209
1210    #[rstest]
1211    #[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
1212    #[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
1213    #[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
1214    #[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
1215    #[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
1216    #[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
1217    #[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
1218    #[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
1219    #[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
1220    fn horizontal_space_takes_into_account_borders_and_padding(
1221        #[case] block: Block,
1222        #[case] padding: Padding,
1223        #[case] horizontal_space: (u16, u16),
1224    ) {
1225        let block = block.padding(padding);
1226        assert_eq!(block.horizontal_space(), horizontal_space);
1227    }
1228
1229    #[test]
1230    const fn border_type_can_be_const() {
1231        const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
1232    }
1233
1234    #[test]
1235    fn block_new() {
1236        assert_eq!(
1237            Block::new(),
1238            Block {
1239                titles: Vec::new(),
1240                titles_style: Style::new(),
1241                titles_alignment: Alignment::Left,
1242                titles_position: Position::Top,
1243                borders: Borders::NONE,
1244                border_style: Style::new(),
1245                border_set: BorderType::Plain.to_border_set(),
1246                style: Style::new(),
1247                padding: Padding::ZERO,
1248            }
1249        );
1250    }
1251
1252    #[test]
1253    const fn block_can_be_const() {
1254        const _DEFAULT_STYLE: Style = Style::new();
1255        const _DEFAULT_PADDING: Padding = Padding::uniform(1);
1256        const _DEFAULT_BLOCK: Block = Block::bordered()
1257            // the following methods are no longer const because they use Into<Style>
1258            // .style(_DEFAULT_STYLE)           // no longer const
1259            // .border_style(_DEFAULT_STYLE)    // no longer const
1260            // .title_style(_DEFAULT_STYLE)     // no longer const
1261            .title_alignment(Alignment::Left)
1262            .title_position(Position::Top)
1263            .padding(_DEFAULT_PADDING);
1264    }
1265
1266    /// Ensure Style from/into works the way a user would use it.
1267    #[test]
1268    fn style_into_works_from_user_view() {
1269        // nominal style
1270        let block = Block::new().style(Style::new().red());
1271        assert_eq!(block.style, Style::new().red());
1272
1273        // auto-convert from Color
1274        let block = Block::new().style(Color::Red);
1275        assert_eq!(block.style, Style::new().red());
1276
1277        // auto-convert from (Color, Color)
1278        let block = Block::new().style((Color::Red, Color::Blue));
1279        assert_eq!(block.style, Style::new().red().on_blue());
1280
1281        // auto-convert from Modifier
1282        let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
1283        assert_eq!(block.style, Style::new().bold().italic());
1284
1285        // auto-convert from (Modifier, Modifier)
1286        let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
1287        assert_eq!(block.style, Style::new().bold().italic().not_dim());
1288
1289        // auto-convert from (Color, Modifier)
1290        let block = Block::new().style((Color::Red, Modifier::BOLD));
1291        assert_eq!(block.style, Style::new().red().bold());
1292
1293        // auto-convert from (Color, Color, Modifier)
1294        let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
1295        assert_eq!(block.style, Style::new().red().on_blue().bold());
1296
1297        // auto-convert from (Color, Color, Modifier, Modifier)
1298        let block = Block::new().style((
1299            Color::Red,
1300            Color::Blue,
1301            Modifier::BOLD | Modifier::ITALIC,
1302            Modifier::DIM,
1303        ));
1304        assert_eq!(
1305            block.style,
1306            Style::new().red().on_blue().bold().italic().not_dim()
1307        );
1308    }
1309
1310    #[test]
1311    fn can_be_stylized() {
1312        let block = Block::new().black().on_white().bold().not_dim();
1313        assert_eq!(
1314            block.style,
1315            Style::default()
1316                .fg(Color::Black)
1317                .bg(Color::White)
1318                .add_modifier(Modifier::BOLD)
1319                .remove_modifier(Modifier::DIM)
1320        );
1321    }
1322
1323    #[test]
1324    fn title() {
1325        use Alignment::*;
1326        use Position::*;
1327        let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1328        #[allow(deprecated)] // until Title is removed
1329        Block::bordered()
1330            .title(Title::from("A").position(Top).alignment(Left))
1331            .title(Title::from("B").position(Top).alignment(Center))
1332            .title(Title::from("C").position(Top).alignment(Right))
1333            .title(Title::from("D").position(Bottom).alignment(Left))
1334            .title(Title::from("E").position(Bottom).alignment(Center))
1335            .title(Title::from("F").position(Bottom).alignment(Right))
1336            .render(buffer.area, &mut buffer);
1337        #[rustfmt::skip]
1338        let expected = Buffer::with_lines([
1339            "┌A───B───C┐",
1340            "│         │",
1341            "└D───E───F┘",
1342        ]);
1343        assert_eq!(buffer, expected);
1344    }
1345
1346    #[test]
1347    fn title_top_bottom() {
1348        let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1349        Block::bordered()
1350            .title_top(Line::raw("A").left_aligned())
1351            .title_top(Line::raw("B").centered())
1352            .title_top(Line::raw("C").right_aligned())
1353            .title_bottom(Line::raw("D").left_aligned())
1354            .title_bottom(Line::raw("E").centered())
1355            .title_bottom(Line::raw("F").right_aligned())
1356            .render(buffer.area, &mut buffer);
1357        #[rustfmt::skip]
1358        let expected = Buffer::with_lines([
1359            "┌A───B───C┐",
1360            "│         │",
1361            "└D───E───F┘",
1362        ]);
1363        assert_eq!(buffer, expected);
1364    }
1365
1366    #[test]
1367    fn title_alignment() {
1368        let tests = vec![
1369            (Alignment::Left, "test    "),
1370            (Alignment::Center, "  test  "),
1371            (Alignment::Right, "    test"),
1372        ];
1373        for (alignment, expected) in tests {
1374            let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1375            Block::new()
1376                .title_alignment(alignment)
1377                .title("test")
1378                .render(buffer.area, &mut buffer);
1379            assert_eq!(buffer, Buffer::with_lines([expected]));
1380        }
1381    }
1382
1383    #[test]
1384    fn title_alignment_overrides_block_title_alignment() {
1385        let tests = vec![
1386            (Alignment::Right, Alignment::Left, "test    "),
1387            (Alignment::Left, Alignment::Center, "  test  "),
1388            (Alignment::Center, Alignment::Right, "    test"),
1389        ];
1390        for (block_title_alignment, alignment, expected) in tests {
1391            let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1392            Block::new()
1393                .title_alignment(block_title_alignment)
1394                .title(Line::from("test").alignment(alignment))
1395                .render(buffer.area, &mut buffer);
1396            assert_eq!(buffer, Buffer::with_lines([expected]));
1397        }
1398    }
1399
1400    /// This is a regression test for bug <https://github.com/ratatui/ratatui/issues/929>
1401    #[test]
1402    fn render_right_aligned_empty_title() {
1403        let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
1404        Block::new()
1405            .title_alignment(Alignment::Right)
1406            .title("")
1407            .render(buffer.area, &mut buffer);
1408        assert_eq!(buffer, Buffer::with_lines(["               "; 3]));
1409    }
1410
1411    #[test]
1412    fn title_position() {
1413        let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
1414        Block::new()
1415            .title_position(Position::Bottom)
1416            .title("test")
1417            .render(buffer.area, &mut buffer);
1418        assert_eq!(buffer, Buffer::with_lines(["    ", "test"]));
1419    }
1420
1421    #[test]
1422    fn title_content_style() {
1423        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1424            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1425            Block::new()
1426                .title_alignment(alignment)
1427                .title("test".yellow())
1428                .render(buffer.area, &mut buffer);
1429            assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1430        }
1431    }
1432
1433    #[test]
1434    fn block_title_style() {
1435        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1436            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1437            Block::new()
1438                .title_alignment(alignment)
1439                .title_style(Style::new().yellow())
1440                .title("test")
1441                .render(buffer.area, &mut buffer);
1442            assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1443        }
1444    }
1445
1446    #[test]
1447    fn title_style_overrides_block_title_style() {
1448        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1449            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1450            Block::new()
1451                .title_alignment(alignment)
1452                .title_style(Style::new().green().on_red())
1453                .title("test".yellow())
1454                .render(buffer.area, &mut buffer);
1455            assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
1456        }
1457    }
1458
1459    #[test]
1460    fn title_border_style() {
1461        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1462        Block::bordered()
1463            .title("test")
1464            .border_style(Style::new().yellow())
1465            .render(buffer.area, &mut buffer);
1466        #[rustfmt::skip]
1467        let mut expected = Buffer::with_lines([
1468            "┌test────┐",
1469            "│        │",
1470            "└────────┘",
1471        ]);
1472        expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
1473        expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
1474        assert_eq!(buffer, expected);
1475    }
1476
1477    #[test]
1478    fn border_type_to_string() {
1479        assert_eq!(format!("{}", BorderType::Plain), "Plain");
1480        assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
1481        assert_eq!(format!("{}", BorderType::Double), "Double");
1482        assert_eq!(format!("{}", BorderType::Thick), "Thick");
1483    }
1484
1485    #[test]
1486    fn border_type_from_str() {
1487        assert_eq!("Plain".parse(), Ok(BorderType::Plain));
1488        assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
1489        assert_eq!("Double".parse(), Ok(BorderType::Double));
1490        assert_eq!("Thick".parse(), Ok(BorderType::Thick));
1491        assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
1492    }
1493
1494    #[test]
1495    fn render_plain_border() {
1496        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1497        Block::bordered()
1498            .border_type(BorderType::Plain)
1499            .render(buffer.area, &mut buffer);
1500        #[rustfmt::skip]
1501        let expected = Buffer::with_lines([
1502            "┌────────┐",
1503            "│        │",
1504            "└────────┘",
1505        ]);
1506        assert_eq!(buffer, expected);
1507    }
1508
1509    #[test]
1510    fn render_rounded_border() {
1511        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1512        Block::bordered()
1513            .border_type(BorderType::Rounded)
1514            .render(buffer.area, &mut buffer);
1515        #[rustfmt::skip]
1516        let expected = Buffer::with_lines([
1517            "╭────────╮",
1518            "│        │",
1519            "╰────────╯",
1520        ]);
1521        assert_eq!(buffer, expected);
1522    }
1523
1524    #[test]
1525    fn render_double_border() {
1526        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1527        Block::bordered()
1528            .border_type(BorderType::Double)
1529            .render(buffer.area, &mut buffer);
1530        #[rustfmt::skip]
1531        let expected = Buffer::with_lines([
1532            "╔════════╗",
1533            "║        ║",
1534            "╚════════╝",
1535        ]);
1536        assert_eq!(buffer, expected);
1537    }
1538
1539    #[test]
1540    fn render_quadrant_inside() {
1541        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1542        Block::bordered()
1543            .border_type(BorderType::QuadrantInside)
1544            .render(buffer.area, &mut buffer);
1545        #[rustfmt::skip]
1546        let expected = Buffer::with_lines([
1547            "▗▄▄▄▄▄▄▄▄▖",
1548            "▐        ▌",
1549            "▝▀▀▀▀▀▀▀▀▘",
1550        ]);
1551        assert_eq!(buffer, expected);
1552    }
1553
1554    #[test]
1555    fn render_border_quadrant_outside() {
1556        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1557        Block::bordered()
1558            .border_type(BorderType::QuadrantOutside)
1559            .render(buffer.area, &mut buffer);
1560        #[rustfmt::skip]
1561        let expected = Buffer::with_lines([
1562            "▛▀▀▀▀▀▀▀▀▜",
1563            "▌        ▐",
1564            "▙▄▄▄▄▄▄▄▄▟",
1565        ]);
1566        assert_eq!(buffer, expected);
1567    }
1568
1569    #[test]
1570    fn render_solid_border() {
1571        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1572        Block::bordered()
1573            .border_type(BorderType::Thick)
1574            .render(buffer.area, &mut buffer);
1575        #[rustfmt::skip]
1576        let expected = Buffer::with_lines([
1577            "┏━━━━━━━━┓",
1578            "┃        ┃",
1579            "┗━━━━━━━━┛",
1580        ]);
1581        assert_eq!(buffer, expected);
1582    }
1583
1584    #[test]
1585    fn render_custom_border_set() {
1586        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1587        Block::bordered()
1588            .border_set(border::Set {
1589                top_left: "1",
1590                top_right: "2",
1591                bottom_left: "3",
1592                bottom_right: "4",
1593                vertical_left: "L",
1594                vertical_right: "R",
1595                horizontal_top: "T",
1596                horizontal_bottom: "B",
1597            })
1598            .render(buffer.area, &mut buffer);
1599        #[rustfmt::skip]
1600        let expected = Buffer::with_lines([
1601            "1TTTTTTTT2",
1602            "L        R",
1603            "3BBBBBBBB4",
1604        ]);
1605        assert_eq!(buffer, expected);
1606    }
1607}