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 alloc::vec::Vec;
9
10use itertools::Itertools;
11use ratatui_core::buffer::Buffer;
12use ratatui_core::layout::{Alignment, Rect};
13use ratatui_core::style::{Style, Styled};
14use ratatui_core::symbols::border;
15use ratatui_core::symbols::merge::MergeStrategy;
16use ratatui_core::text::Line;
17use ratatui_core::widgets::Widget;
18use strum::{Display, EnumString};
19
20pub use self::padding::Padding;
21use crate::borders::{BorderType, Borders};
22
23mod padding;
24
25/// A widget that renders borders, titles, and padding around other widgets.
26///
27/// A `Block` is a foundational widget that creates visual containers by drawing borders around an
28/// area. It serves as a wrapper or frame for other widgets, providing structure and visual
29/// separation in terminal UIs. Most built-in widgets in Ratatui use a pattern where they accept an
30/// optional `Block` parameter that wraps the widget's content.
31///
32/// When a widget renders with a block, the widget's style is applied first, then the block's style,
33/// and finally the widget's content is rendered within the inner area calculated by the block. This
34/// layered approach allows for flexible styling where the block can provide background colors,
35/// borders, and padding while the inner widget handles its own content styling.
36///
37/// Multiple blocks can be nested within each other. The [`Block::inner`] method calculates the area
38/// available for content after accounting for borders, titles, and padding, making it easy to nest
39/// blocks or position widgets within a block's boundaries.
40///
41/// # Constructor Methods
42///
43/// - [`Block::new`] - Creates a block with no borders or padding
44/// - [`Block::bordered`] - Creates a block with all borders enabled
45///
46/// # Border Configuration
47///
48/// - [`Block::borders`] - Specifies which borders to display
49/// - [`Block::border_style`] - Sets the style of the borders
50/// - [`Block::border_type`] - Sets border symbols (single, double, thick, rounded, etc.)
51/// - [`Block::border_set`] - Sets custom border symbols as a [`border::Set`]
52/// - [`Block::merge_borders`] - Controls how borders merge with adjacent blocks
53///
54/// # Title Configuration
55///
56/// - [`Block::title`] - Adds a title to the block
57/// - [`Block::title_top`] - Adds a title to the top of the block
58/// - [`Block::title_bottom`] - Adds a title to the bottom of the block
59/// - [`Block::title_alignment`] - Sets default alignment for all titles
60/// - [`Block::title_style`] - Sets the style for all titles
61/// - [`Block::title_position`] - Sets default position for titles
62///
63/// # Styling and Layout
64///
65/// - [`Block::style`] - Sets the base style of the block
66/// - [`Block::padding`] - Adds internal padding within the borders
67/// - [`Block::inner`] - Calculates the inner area available for content
68///
69/// # Title Behavior
70///
71/// You can add multiple titles to a block, and they will be rendered with spaces separating titles
72/// that share the same position or alignment. When both centered and non-centered titles exist, the
73/// centered space is calculated based on the full width of the block.
74///
75/// Titles are set using the `.title`, `.title_top`, and `.title_bottom` methods. These methods
76/// accept a string or any type that can be converted into a [`Line`], such as a string slice,
77/// `String`, or a vector of [`Span`]s. To control the alignment of a title (left, center, right),
78/// pass a `Line` with the desired alignment, e.g. `Line::from("Title").centered()`.
79///
80/// By default, `.title` places the title at the top of the block, but you can use `.title_top` or
81/// `.title_bottom` to explicitly set the position. The default alignment for all titles can be set
82/// with [`Block::title_alignment`], and the default position for all titles can be set with
83/// [`Block::title_position`].
84///
85/// Note that prior to `v0.30.0`, the `block::Title` struct was used to create titles. This struct
86/// has been removed. The new recommended approach is to use [`Line`] with a specific alignment for
87/// the title's content and the [`Block::title_top`] and [`Block::title_bottom`] methods for
88/// positioning.
89///
90/// Titles avoid being rendered in corners when borders are present, but will align to edges when no
91/// border exists on that side:
92///
93/// ```plain
94/// ┌With at least a left border───
95///
96/// Without left border───
97/// ```
98///
99/// # Nesting Widgets with `inner`
100///
101/// The [`Block::inner`] method computes the area inside the block after accounting for borders,
102/// titles, and padding. This allows you to nest widgets inside a block by rendering the block
103/// first, then rendering other widgets in the returned inner area.
104///
105/// For example, you can nest a block inside another block:
106///
107/// ```
108/// use ratatui::Frame;
109/// use ratatui::widgets::Block;
110///
111/// # fn render_nested_block(frame: &mut Frame) {
112/// let outer_block = Block::bordered().title("Outer");
113/// let inner_block = Block::bordered().title("Inner");
114///
115/// let outer_area = frame.area();
116/// let inner_area = outer_block.inner(outer_area);
117///
118/// frame.render_widget(outer_block, outer_area);
119/// frame.render_widget(inner_block, inner_area);
120/// # }
121/// ```
122///
123/// You can also use the standard [`Layout`] functionality to further subdivide the inner area and
124/// lay out multiple widgets inside a block.
125///
126/// # Integration with Other Widgets
127///
128/// Most widgets in Ratatui accept a block parameter. For example, [`Paragraph`], [`List`],
129/// [`Table`], and other widgets can be wrapped with a block:
130///
131/// ```
132/// use ratatui::widgets::{Block, Paragraph};
133///
134/// let paragraph = Paragraph::new("Hello, world!").block(Block::bordered().title("My Paragraph"));
135/// ```
136///
137/// This pattern allows widgets to focus on their content while blocks handle the visual framing.
138///
139/// # Styling
140///
141/// Styles are applied in a specific order: first the block's base style, then border styles, then
142/// title styles, and finally any content widget styles. This layered approach allows for flexible
143/// styling where outer styles provide defaults that inner styles can override.
144///
145/// `Block` implements [`Stylize`](ratatui_core::style::Stylize), allowing you to use style
146/// shorthand methods:
147///
148/// ```
149/// use ratatui::style::Stylize;
150/// use ratatui::widgets::Block;
151///
152/// let block = Block::bordered().red().on_white().bold();
153/// ```
154///
155/// # Examples
156///
157/// Create a simple bordered block:
158///
159/// ```
160/// use ratatui::widgets::Block;
161///
162/// let block = Block::bordered().title("My Block");
163/// ```
164///
165/// Create a block with custom border styling:
166///
167/// ```
168/// use ratatui::style::{Color, Style, Stylize};
169/// use ratatui::widgets::{Block, BorderType};
170///
171/// let block = Block::bordered()
172///     .title("Styled Block")
173///     .border_type(BorderType::Rounded)
174///     .border_style(Style::new().cyan())
175///     .style(Style::new().on_black());
176/// ```
177///
178/// Use a block to wrap another widget:
179///
180/// ```
181/// use ratatui::widgets::{Block, Paragraph};
182///
183/// let paragraph = Paragraph::new("Hello, world!").block(Block::bordered().title("Greeting"));
184/// ```
185///
186/// Add multiple titles with different alignments:
187///
188/// ```
189/// use ratatui::text::Line;
190/// use ratatui::widgets::Block;
191///
192/// let block = Block::bordered()
193///     .title_top(Line::from("Left").left_aligned())
194///     .title_top(Line::from("Center").centered())
195///     .title_top(Line::from("Right").right_aligned())
196///     .title_bottom("Status: OK");
197/// ```
198///
199/// # See Also
200///
201/// - [Block recipe] - Visual examples and common patterns (on the ratatui website)
202/// - [Collapse borders recipe] - Techniques for creating seamless layouts (on the ratatui website)
203/// - [`MergeStrategy`] - Controls how borders merge with adjacent elements
204///
205/// [Block recipe]: https://ratatui.rs/recipes/widgets/block/
206/// [Collapse borders recipe]: https://ratatui.rs/recipes/layout/collapse-borders/
207/// [`Paragraph`]: crate::paragraph::Paragraph
208/// [`Span`]: ratatui_core::text::Span
209/// [`Table`]: crate::table::Table
210/// [`Stylize`]: ratatui_core::style::Stylize
211/// [`List`]: crate::list::List
212/// [`Layout`]: ratatui_core::layout::Layout
213#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
214pub struct Block<'a> {
215    /// List of titles
216    titles: Vec<(Option<TitlePosition>, Line<'a>)>,
217    /// The style to be patched to all titles of the block
218    titles_style: Style,
219    /// The default alignment of the titles that don't have one
220    titles_alignment: Alignment,
221    /// The default position of the titles that don't have one
222    titles_position: TitlePosition,
223    /// Visible borders
224    borders: Borders,
225    /// Border style
226    border_style: Style,
227    /// The symbols used to render the border. The default is plain lines but one can choose to
228    /// have rounded or doubled lines instead or a custom set of symbols
229    border_set: border::Set<'a>,
230    /// Widget style
231    style: Style,
232    /// Block padding
233    padding: Padding,
234    /// Border merging strategy
235    merge_borders: MergeStrategy,
236}
237
238/// Defines the position of the title.
239///
240/// The title can be positioned on top or at the bottom of the block.
241///
242/// # Example
243///
244/// ```
245/// use ratatui::widgets::{Block, TitlePosition};
246///
247/// Block::bordered()
248///     .title_position(TitlePosition::Top)
249///     .title("Top Title");
250/// Block::bordered()
251///     .title_position(TitlePosition::Bottom)
252///     .title("Bottom Title");
253/// ```
254#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
255pub enum TitlePosition {
256    /// Position the title at the top of the block.
257    #[default]
258    Top,
259    /// Position the title at the bottom of the block.
260    Bottom,
261}
262
263impl<'a> Block<'a> {
264    /// Creates a new block with no [`Borders`] or [`Padding`].
265    pub const fn new() -> Self {
266        Self {
267            titles: Vec::new(),
268            titles_style: Style::new(),
269            titles_alignment: Alignment::Left,
270            titles_position: TitlePosition::Top,
271            borders: Borders::NONE,
272            border_style: Style::new(),
273            border_set: BorderType::Plain.to_border_set(),
274            style: Style::new(),
275            padding: Padding::ZERO,
276            merge_borders: MergeStrategy::Replace,
277        }
278    }
279
280    /// Create a new block with [all borders](Borders::ALL) shown
281    ///
282    /// ```
283    /// use ratatui::widgets::{Block, Borders};
284    ///
285    /// assert_eq!(Block::bordered(), Block::new().borders(Borders::ALL));
286    /// ```
287    pub const fn bordered() -> Self {
288        let mut block = Self::new();
289        block.borders = Borders::ALL;
290        block
291    }
292
293    /// Adds a title to the block using the default position.
294    ///
295    /// The position of the title is determined by the `title_position` field of the block, which
296    /// defaults to `Top`. This can be changed using the [`Block::title_position`] method. For
297    /// explicit positioning, use [`Block::title_top`] or [`Block::title_bottom`].
298    ///
299    /// The `title` function allows you to add a title to the block. You can call this function
300    /// multiple times to add multiple titles.
301    ///
302    /// Each title will be rendered with a single space separating titles that are in the same
303    /// position or alignment. When both centered and non-centered titles are rendered, the centered
304    /// space is calculated based on the full width of the block, rather than the leftover width.
305    ///
306    /// You can provide any type that can be converted into [`Line`] including: strings, string
307    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](ratatui_core::text::Span), or
308    /// vectors of [spans](ratatui_core::text::Span) (`Vec<Span>`).
309    ///
310    /// By default, the titles will avoid being rendered in the corners of the block but will align
311    /// against the left or right edge of the block if there is no border on that edge. The
312    /// following demonstrates this behavior, notice the second title is one character off to the
313    /// left.
314    ///
315    /// ```plain
316    /// ┌With at least a left border───
317    ///
318    /// Without left border───
319    /// ```
320    ///
321    /// Note: If the block is too small and multiple titles overlap, the border might get cut off at
322    /// a corner.
323    ///
324    /// # Examples
325    ///
326    /// See the [Block example] for a visual representation of how the various borders and styles
327    /// look when rendered.
328    ///
329    /// The following example demonstrates:
330    /// - Default title alignment
331    /// - Multiple titles (notice "Center" is centered according to the full with of the block, not
332    ///   the leftover space)
333    /// - Two titles with the same alignment (notice the left titles are separated)
334    /// ```
335    /// use ratatui::text::Line;
336    /// use ratatui::widgets::Block;
337    ///
338    /// Block::bordered()
339    ///     .title("Title")
340    ///     .title(Line::from("Left").left_aligned())
341    ///     .title(Line::from("Right").right_aligned())
342    ///     .title(Line::from("Center").centered());
343    /// ```
344    ///
345    /// # See also
346    ///
347    /// Titles attached to a block can have default behaviors. See
348    /// - [`Block::title_style`]
349    /// - [`Block::title_alignment`]
350    ///
351    /// # History
352    ///
353    /// In previous releases of Ratatui this method accepted `Into<Title>` instead of
354    /// [`Into<Line>`]. We found that storing the position in the block and the alignment in the
355    /// line better reflects the intended use of the block and its titles. See
356    /// <https://github.com/ratatui/ratatui/issues/738> for more information.
357    ///
358    /// [Block example]: https://github.com/ratatui/ratatui/blob/main/examples/README.md#block
359    #[must_use = "method moves the value of self and returns the modified value"]
360    pub fn title<T>(mut self, title: T) -> Self
361    where
362        T: Into<Line<'a>>,
363    {
364        self.titles.push((None, title.into()));
365        self
366    }
367
368    /// Adds a title to the top of the block.
369    ///
370    /// You can provide any type that can be converted into [`Line`] including: strings, string
371    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](ratatui_core::text::Span), or
372    /// vectors of [spans](ratatui_core::text::Span) (`Vec<Span>`).
373    ///
374    /// # Example
375    ///
376    /// ```
377    /// use ratatui::{ widgets::Block, text::Line };
378    ///
379    /// Block::bordered()
380    ///     .title_top("Left1") // By default in the top left corner
381    ///     .title_top(Line::from("Left2").left_aligned())
382    ///     .title_top(Line::from("Right").right_aligned())
383    ///     .title_top(Line::from("Center").centered());
384    ///
385    /// // Renders
386    /// // ┌Left1─Left2───Center─────────Right┐
387    /// // │                                  │
388    /// // └──────────────────────────────────┘
389    /// ```
390    #[must_use = "method moves the value of self and returns the modified value"]
391    pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
392        let line = title.into();
393        self.titles.push((Some(TitlePosition::Top), line));
394        self
395    }
396
397    /// Adds a title to the bottom of the block.
398    ///
399    /// You can provide any type that can be converted into [`Line`] including: strings, string
400    /// slices (`&str`), borrowed strings (`Cow<str>`), [spans](ratatui_core::text::Span), or
401    /// vectors of [spans](ratatui_core::text::Span) (`Vec<Span>`).
402    ///
403    /// # Example
404    ///
405    /// ```
406    /// use ratatui::{ widgets::Block, text::Line };
407    ///
408    /// Block::bordered()
409    ///     .title_bottom("Left1") // By default in the top left corner
410    ///     .title_bottom(Line::from("Left2").left_aligned())
411    ///     .title_bottom(Line::from("Right").right_aligned())
412    ///     .title_bottom(Line::from("Center").centered());
413    ///
414    /// // Renders
415    /// // ┌──────────────────────────────────┐
416    /// // │                                  │
417    /// // └Left1─Left2───Center─────────Right┘
418    /// ```
419    #[must_use = "method moves the value of self and returns the modified value"]
420    pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
421        let line = title.into();
422        self.titles.push((Some(TitlePosition::Bottom), line));
423        self
424    }
425
426    /// Applies the style to all titles.
427    ///
428    /// This style will be applied to all titles of the block. If a title has a style set, it will
429    /// be applied after this style. This style will be applied after any [`Block::style`] or
430    /// [`Block::border_style`] is applied.
431    ///
432    /// See [`Style`] for more information on how merging styles works.
433    ///
434    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
435    /// your own type that implements [`Into<Style>`]).
436    ///
437    /// [`Color`]: ratatui_core::style::Color
438    #[must_use = "method moves the value of self and returns the modified value"]
439    pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
440        self.titles_style = style.into();
441        self
442    }
443
444    /// Sets the default [`Alignment`] for all block titles.
445    ///
446    /// Titles that explicitly set an [`Alignment`] will ignore this.
447    ///
448    /// # Example
449    ///
450    /// This example aligns all titles in the center except the "right" title which explicitly sets
451    /// [`Alignment::Right`].
452    /// ```
453    /// use ratatui::layout::Alignment;
454    /// use ratatui::text::Line;
455    /// use ratatui::widgets::Block;
456    ///
457    /// Block::bordered()
458    ///     .title_alignment(Alignment::Center)
459    ///     // This title won't be aligned in the center
460    ///     .title(Line::from("right").right_aligned())
461    ///     .title("foo")
462    ///     .title("bar");
463    /// ```
464    #[must_use = "method moves the value of self and returns the modified value"]
465    pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
466        self.titles_alignment = alignment;
467        self
468    }
469
470    /// Sets the default [`TitlePosition`] for all block titles.
471    ///
472    /// # Example
473    ///
474    /// This example positions all titles on the bottom by default. The "top" title explicitly sets
475    /// its position to `Top`, so it is not affected. The "foo" and "bar" titles will be positioned
476    /// at the bottom.
477    ///
478    /// ```
479    /// use ratatui::widgets::{Block, TitlePosition};
480    ///
481    /// Block::bordered()
482    ///     .title_position(TitlePosition::Bottom)
483    ///     .title("foo") // will be at the bottom
484    ///     .title_top("top") // will be at the top
485    ///     .title("bar"); // will be at the bottom
486    /// ```
487    #[must_use = "method moves the value of self and returns the modified value"]
488    pub const fn title_position(mut self, position: TitlePosition) -> Self {
489        self.titles_position = position;
490        self
491    }
492
493    /// Defines the style of the borders.
494    ///
495    /// This style is applied only to the areas covered by borders, and is applied to the block
496    /// after any [`Block::style`] is applied.
497    ///
498    /// See [`Style`] for more information on how merging styles works.
499    ///
500    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
501    /// your own type that implements [`Into<Style>`]).
502    ///
503    /// # Example
504    ///
505    /// This example shows a `Block` with blue borders.
506    /// ```
507    /// use ratatui::style::{Style, Stylize};
508    /// use ratatui::widgets::Block;
509    /// Block::bordered().border_style(Style::new().blue());
510    /// ```
511    ///
512    /// [`Color`]: ratatui_core::style::Color
513    #[must_use = "method moves the value of self and returns the modified value"]
514    pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
515        self.border_style = style.into();
516        self
517    }
518
519    /// Defines the style of the entire block.
520    ///
521    /// This is the most generic [`Style`] a block can receive, it will be merged with any other
522    /// more specific styles. Elements can be styled further with [`Block::title_style`] and
523    /// [`Block::border_style`], which will be applied on top of this style. If the block is used as
524    /// a container for another widget (e.g. a [`Paragraph`]), then the style of the widget is
525    /// generally applied before this style.
526    ///
527    /// See [`Style`] for more information on how merging styles works.
528    ///
529    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
530    /// your own type that implements [`Into<Style>`]).
531    ///
532    /// # Example
533    ///
534    /// ```
535    /// use ratatui::style::{Color, Style, Stylize};
536    /// use ratatui::widgets::{Block, Paragraph};
537    ///
538    /// let block = Block::new().style(Style::new().red().on_black());
539    ///
540    /// // For border and title you can additionally apply styles on top of the block level style.
541    /// let block = Block::new()
542    ///     .style(Style::new().red().bold().italic())
543    ///     .border_style(Style::new().not_italic()) // will be red and bold
544    ///     .title_style(Style::new().not_bold()) // will be red and italic
545    ///     .title("Title");
546    ///
547    /// // To style the inner widget, you can style the widget itself.
548    /// let paragraph = Paragraph::new("Content")
549    ///     .block(block)
550    ///     .style(Style::new().white().not_bold()); // will be white, and italic
551    /// ```
552    ///
553    /// [`Paragraph`]: crate::paragraph::Paragraph
554    /// [`Color`]: ratatui_core::style::Color
555    #[must_use = "method moves the value of self and returns the modified value"]
556    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
557        self.style = style.into();
558        self
559    }
560
561    /// Defines which borders to display.
562    ///
563    /// [`Borders`] can also be styled with [`Block::border_style`] and [`Block::border_type`].
564    ///
565    /// # Examples
566    ///
567    /// Display left and right borders.
568    /// ```
569    /// use ratatui::widgets::{Block, Borders};
570    /// Block::new().borders(Borders::LEFT | Borders::RIGHT);
571    /// ```
572    ///
573    /// To show all borders you can abbreviate this with [`Block::bordered`]
574    #[must_use = "method moves the value of self and returns the modified value"]
575    pub const fn borders(mut self, flag: Borders) -> Self {
576        self.borders = flag;
577        self
578    }
579
580    /// Sets the symbols used to display the border (e.g. single line, double line, thick or
581    /// rounded borders).
582    ///
583    /// Setting this overwrites any custom [`border_set`](Block::border_set) that was set.
584    ///
585    /// See [`BorderType`] for the full list of available symbols.
586    ///
587    /// # Examples
588    ///
589    /// ```
590    /// use ratatui::widgets::{Block, BorderType};
591    /// Block::bordered()
592    ///     .border_type(BorderType::Rounded)
593    ///     .title("Block");
594    /// // Renders
595    /// // ╭Block╮
596    /// // │     │
597    /// // ╰─────╯
598    /// ```
599    #[must_use = "method moves the value of self and returns the modified value"]
600    pub const fn border_type(mut self, border_type: BorderType) -> Self {
601        self.border_set = border_type.to_border_set();
602        self
603    }
604
605    /// Sets the symbols used to display the border as a [`ratatui_core::symbols::border::Set`].
606    ///
607    /// Setting this overwrites any [`border_type`](Block::border_type) that was set.
608    ///
609    /// # Examples
610    ///
611    /// ```
612    /// use ratatui::{widgets::Block, symbols};
613    ///
614    /// Block::bordered().border_set(symbols::border::DOUBLE).title("Block");
615    /// // Renders
616    /// // ╔Block╗
617    /// // ║     ║
618    /// // ╚═════╝
619    #[must_use = "method moves the value of self and returns the modified value"]
620    pub const fn border_set(mut self, border_set: border::Set<'a>) -> Self {
621        self.border_set = border_set;
622        self
623    }
624
625    /// Defines the padding inside a `Block`.
626    ///
627    /// See [`Padding`] for more information.
628    ///
629    /// # Examples
630    ///
631    /// This renders a `Block` with no padding (the default).
632    /// ```
633    /// use ratatui::widgets::{Block, Padding};
634    ///
635    /// Block::bordered().padding(Padding::ZERO);
636    /// // Renders
637    /// // ┌───────┐
638    /// // │content│
639    /// // └───────┘
640    /// ```
641    ///
642    /// This example shows a `Block` with padding left and right ([`Padding::horizontal`]).
643    /// Notice the two spaces before and after the content.
644    /// ```
645    /// use ratatui::widgets::{Block, Padding};
646    ///
647    /// Block::bordered().padding(Padding::horizontal(2));
648    /// // Renders
649    /// // ┌───────────┐
650    /// // │  content  │
651    /// // └───────────┘
652    /// ```
653    #[must_use = "method moves the value of self and returns the modified value"]
654    pub const fn padding(mut self, padding: Padding) -> Self {
655        self.padding = padding;
656        self
657    }
658
659    /// Sets the block's [`MergeStrategy`] for overlapping characters, defaulting to [`Replace`].
660    ///
661    /// Changing the strategy to [`Exact`] or [`Fuzzy`] collapses border characters that intersect
662    /// with any previously rendered borders.
663    ///
664    /// For more information and examples, see the [collapse borders recipe] and [`MergeStrategy`]
665    /// docs.
666    ///
667    /// # Example
668    ///
669    /// ```
670    /// use ratatui::symbols::merge::MergeStrategy;
671    /// # use ratatui::widgets::{Block, BorderType};
672    ///
673    /// // Given several blocks with plain borders (1)
674    /// Block::bordered();
675    /// // and other blocks with thick borders (2) which are rendered on top of the first
676    /// Block::bordered()
677    ///     .border_type(BorderType::Thick)
678    ///     .merge_borders(MergeStrategy::Exact);
679    /// ```
680    ///
681    /// Rendering these blocks with `MergeStrategy::Exact` or `MergeStrategy::Fuzzy` will collapse
682    /// the borders, resulting in a clean layout without connected borders.
683    ///
684    /// ```plain
685    /// ┌───┐    ┌───┐  ┌───┲━━━┓┌───┐
686    /// │   │    │ 1 │  │   ┃   ┃│   │
687    /// │ 1 │    │ ┏━┿━┓│ 1 ┃ 2 ┃│ 1 │
688    /// │   │    │ ┃ │ ┃│   ┃   ┃│   │
689    /// └───╆━━━┓└─╂─┘ ┃└───┺━━━┛┢━━━┪
690    ///     ┃   ┃  ┃ 2 ┃         ┃   ┃
691    ///     ┃ 2 ┃  ┗━━━┛         ┃ 2 ┃
692    ///     ┃   ┃                ┃   ┃
693    ///     ┗━━━┛                ┗━━━┛
694    /// ```
695    ///
696    /// [collapse borders recipe]: https://ratatui.rs/recipes/layout/collapse-borders/
697    /// [`Replace`]: MergeStrategy::Replace
698    /// [`Exact`]: MergeStrategy::Exact
699    /// [`Fuzzy`]: MergeStrategy::Fuzzy
700    #[must_use = "method moves the value of self and returns the modified value"]
701    pub const fn merge_borders(mut self, strategy: MergeStrategy) -> Self {
702        self.merge_borders = strategy;
703        self
704    }
705
706    /// Computes the inner area of a block after subtracting space for borders, titles, and padding.
707    ///
708    /// # Examples
709    ///
710    /// Draw a block nested within another block
711    /// ```
712    /// use ratatui::Frame;
713    /// use ratatui::widgets::Block;
714    ///
715    /// # fn render_nested_block(frame: &mut Frame) {
716    /// let outer_block = Block::bordered().title("Outer");
717    /// let inner_block = Block::bordered().title("Inner");
718    ///
719    /// let outer_area = frame.area();
720    /// let inner_area = outer_block.inner(outer_area);
721    ///
722    /// frame.render_widget(outer_block, outer_area);
723    /// frame.render_widget(inner_block, inner_area);
724    /// # }
725    /// // Renders
726    /// // ┌Outer────────┐
727    /// // │┌Inner──────┐│
728    /// // ││           ││
729    /// // │└───────────┘│
730    /// // └─────────────┘
731    /// ```
732    pub fn inner(&self, area: Rect) -> Rect {
733        let mut inner = area;
734        if self.borders.intersects(Borders::LEFT) {
735            inner.x = inner.x.saturating_add(1).min(inner.right());
736            inner.width = inner.width.saturating_sub(1);
737        }
738        if self.borders.intersects(Borders::TOP) || self.has_title_at_position(TitlePosition::Top) {
739            inner.y = inner.y.saturating_add(1).min(inner.bottom());
740            inner.height = inner.height.saturating_sub(1);
741        }
742        if self.borders.intersects(Borders::RIGHT) {
743            inner.width = inner.width.saturating_sub(1);
744        }
745        if self.borders.intersects(Borders::BOTTOM)
746            || self.has_title_at_position(TitlePosition::Bottom)
747        {
748            inner.height = inner.height.saturating_sub(1);
749        }
750
751        inner.x = inner.x.saturating_add(self.padding.left);
752        inner.y = inner.y.saturating_add(self.padding.top);
753
754        inner.width = inner
755            .width
756            .saturating_sub(self.padding.left + self.padding.right);
757        inner.height = inner
758            .height
759            .saturating_sub(self.padding.top + self.padding.bottom);
760
761        inner
762    }
763
764    fn has_title_at_position(&self, position: TitlePosition) -> bool {
765        self.titles
766            .iter()
767            .any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
768    }
769}
770
771impl Widget for Block<'_> {
772    fn render(self, area: Rect, buf: &mut Buffer) {
773        Widget::render(&self, area, buf);
774    }
775}
776
777impl Widget for &Block<'_> {
778    fn render(self, area: Rect, buf: &mut Buffer) {
779        let area = area.intersection(buf.area);
780        if area.is_empty() {
781            return;
782        }
783        buf.set_style(area, self.style);
784        self.render_borders(area, buf);
785        self.render_titles(area, buf);
786    }
787}
788
789impl Block<'_> {
790    fn render_borders(&self, area: Rect, buf: &mut Buffer) {
791        self.render_sides(area, buf);
792        self.render_corners(area, buf);
793    }
794
795    fn render_sides(&self, area: Rect, buf: &mut Buffer) {
796        let left = area.left();
797        let top = area.top();
798        // area.right() and area.bottom() are outside the rect, subtract 1 to get the last row/col
799        let right = area.right() - 1;
800        let bottom = area.bottom() - 1;
801
802        // The first and last element of each line are not drawn when there is an adjacent line as
803        // this would cause the corner to initially be merged with a side character and then a
804        // corner character to be drawn on top of it. Some merge strategies would not produce a
805        // correct character in that case.
806        let is_replace = self.merge_borders != MergeStrategy::Replace;
807        let left_inset = left + u16::from(is_replace && self.borders.contains(Borders::LEFT));
808        let top_inset = top + u16::from(is_replace && self.borders.contains(Borders::TOP));
809        let right_inset = right - u16::from(is_replace && self.borders.contains(Borders::RIGHT));
810        let bottom_inset = bottom - u16::from(is_replace && self.borders.contains(Borders::BOTTOM));
811
812        let sides = [
813            (
814                Borders::LEFT,
815                left..=left,
816                top_inset..=bottom_inset,
817                self.border_set.vertical_left,
818            ),
819            (
820                Borders::TOP,
821                left_inset..=right_inset,
822                top..=top,
823                self.border_set.horizontal_top,
824            ),
825            (
826                Borders::RIGHT,
827                right..=right,
828                top_inset..=bottom_inset,
829                self.border_set.vertical_right,
830            ),
831            (
832                Borders::BOTTOM,
833                left_inset..=right_inset,
834                bottom..=bottom,
835                self.border_set.horizontal_bottom,
836            ),
837        ];
838        for (border, x_range, y_range, symbol) in sides {
839            if self.borders.contains(border) {
840                for x in x_range {
841                    for y in y_range.clone() {
842                        buf[(x, y)]
843                            .merge_symbol(symbol, self.merge_borders)
844                            .set_style(self.border_style);
845                    }
846                }
847            }
848        }
849    }
850
851    fn render_corners(&self, area: Rect, buf: &mut Buffer) {
852        let corners = [
853            (
854                Borders::RIGHT | Borders::BOTTOM,
855                area.right() - 1,
856                area.bottom() - 1,
857                self.border_set.bottom_right,
858            ),
859            (
860                Borders::RIGHT | Borders::TOP,
861                area.right() - 1,
862                area.top(),
863                self.border_set.top_right,
864            ),
865            (
866                Borders::LEFT | Borders::BOTTOM,
867                area.left(),
868                area.bottom() - 1,
869                self.border_set.bottom_left,
870            ),
871            (
872                Borders::LEFT | Borders::TOP,
873                area.left(),
874                area.top(),
875                self.border_set.top_left,
876            ),
877        ];
878
879        for (border, x, y, symbol) in corners {
880            if self.borders.contains(border) {
881                buf[(x, y)]
882                    .merge_symbol(symbol, self.merge_borders)
883                    .set_style(self.border_style);
884            }
885        }
886    }
887    fn render_titles(&self, area: Rect, buf: &mut Buffer) {
888        self.render_title_position(TitlePosition::Top, area, buf);
889        self.render_title_position(TitlePosition::Bottom, area, buf);
890    }
891
892    fn render_title_position(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
893        // NOTE: the order in which these functions are called defines the overlapping behavior
894        self.render_left_titles(position, area, buf);
895        self.render_center_titles(position, area, buf);
896        self.render_right_titles(position, area, buf);
897    }
898
899    /// Render titles aligned to the right of the block
900    ///
901    /// Currently (due to the way lines are truncated), the right side of the leftmost title will
902    /// be cut off if the block is too small to fit all titles. This is not ideal and should be
903    /// the left side of that leftmost that is cut off. This is due to the line being truncated
904    /// incorrectly. See <https://github.com/ratatui/ratatui/issues/932>
905    #[expect(clippy::similar_names)]
906    fn render_right_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
907        let titles = self.filtered_titles(position, Alignment::Right);
908        let mut titles_area = self.titles_area(area, position);
909
910        // render titles in reverse order to align them to the right
911        for title in titles.rev() {
912            if titles_area.is_empty() {
913                break;
914            }
915            let title_width = title.width() as u16;
916            let title_area = Rect {
917                x: titles_area
918                    .right()
919                    .saturating_sub(title_width)
920                    .max(titles_area.left()),
921                width: title_width.min(titles_area.width),
922                ..titles_area
923            };
924            buf.set_style(title_area, self.titles_style);
925            title.render(title_area, buf);
926
927            // bump the width of the titles area to the left
928            titles_area.width = titles_area
929                .width
930                .saturating_sub(title_width)
931                .saturating_sub(1); // space between titles
932        }
933    }
934
935    /// Render titles in the center of the block
936    fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
937        let area = self.titles_area(area, position);
938        let titles = self
939            .filtered_titles(position, Alignment::Center)
940            .collect_vec();
941        // titles are rendered with a space after each title except the last one
942        let total_width = titles
943            .iter()
944            .map(|title| title.width() as u16 + 1)
945            .sum::<u16>()
946            .saturating_sub(1);
947
948        if total_width <= area.width {
949            self.render_centered_titles_without_truncation(titles, total_width, area, buf);
950        } else {
951            self.render_centered_titles_with_truncation(titles, total_width, area, buf);
952        }
953    }
954
955    fn render_centered_titles_without_truncation(
956        &self,
957        titles: Vec<&Line<'_>>,
958        total_width: u16,
959        area: Rect,
960        buf: &mut Buffer,
961    ) {
962        // titles fit in the area, center them
963        let x = area.left() + area.width.saturating_sub(total_width) / 2;
964        let mut area = Rect { x, ..area };
965        for title in titles {
966            let width = title.width() as u16;
967            let title_area = Rect { width, ..area };
968            buf.set_style(title_area, self.titles_style);
969            title.render(title_area, buf);
970            // Move the rendering cursor to the right, leaving 1 column space.
971            area.x = area.x.saturating_add(width + 1);
972            area.width = area.width.saturating_sub(width + 1);
973        }
974    }
975
976    fn render_centered_titles_with_truncation(
977        &self,
978        titles: Vec<&Line<'_>>,
979        total_width: u16,
980        mut area: Rect,
981        buf: &mut Buffer,
982    ) {
983        // titles do not fit in the area, truncate the left side using an offset. The right side
984        // is truncated by the area width.
985        let mut offset = total_width.saturating_sub(area.width) / 2;
986        for title in titles {
987            if area.is_empty() {
988                break;
989            }
990            let width = area.width.min(title.width() as u16).saturating_sub(offset);
991            let title_area = Rect { width, ..area };
992            buf.set_style(title_area, self.titles_style);
993            if offset > 0 {
994                // truncate the left side of the title to fit the area
995                title.clone().right_aligned().render(title_area, buf);
996                offset = offset.saturating_sub(width).saturating_sub(1);
997            } else {
998                // truncate the right side of the title to fit the area if needed
999                title.clone().left_aligned().render(title_area, buf);
1000            }
1001            // Leave 1 column of spacing between titles.
1002            area.x = area.x.saturating_add(width + 1);
1003            area.width = area.width.saturating_sub(width + 1);
1004        }
1005    }
1006
1007    /// Render titles aligned to the left of the block
1008    #[expect(clippy::similar_names)]
1009    fn render_left_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
1010        let titles = self.filtered_titles(position, Alignment::Left);
1011        let mut titles_area = self.titles_area(area, position);
1012        for title in titles {
1013            if titles_area.is_empty() {
1014                break;
1015            }
1016            let title_width = title.width() as u16;
1017            let title_area = Rect {
1018                width: title_width.min(titles_area.width),
1019                ..titles_area
1020            };
1021            buf.set_style(title_area, self.titles_style);
1022            title.render(title_area, buf);
1023
1024            // bump the titles area to the right and reduce its width
1025            titles_area.x = titles_area.x.saturating_add(title_width + 1);
1026            titles_area.width = titles_area.width.saturating_sub(title_width + 1);
1027        }
1028    }
1029
1030    /// An iterator over the titles that match the position and alignment
1031    fn filtered_titles(
1032        &self,
1033        position: TitlePosition,
1034        alignment: Alignment,
1035    ) -> impl DoubleEndedIterator<Item = &Line<'_>> {
1036        self.titles
1037            .iter()
1038            .filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
1039            .filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
1040            .map(|(_, line)| line)
1041    }
1042
1043    /// An area that is one line tall and spans the width of the block excluding the borders and
1044    /// is positioned at the top or bottom of the block.
1045    fn titles_area(&self, area: Rect, position: TitlePosition) -> Rect {
1046        let left_border = u16::from(self.borders.contains(Borders::LEFT));
1047        let right_border = u16::from(self.borders.contains(Borders::RIGHT));
1048        Rect {
1049            x: area.left() + left_border,
1050            y: match position {
1051                TitlePosition::Top => area.top(),
1052                TitlePosition::Bottom => area.bottom() - 1,
1053            },
1054            width: area
1055                .width
1056                .saturating_sub(left_border)
1057                .saturating_sub(right_border),
1058            height: 1,
1059        }
1060    }
1061
1062    /// Calculate the left, and right space the [`Block`] will take up.
1063    ///
1064    /// The result takes the [`Block`]'s, [`Borders`], and [`Padding`] into account.
1065    pub(crate) fn horizontal_space(&self) -> (u16, u16) {
1066        let left = self
1067            .padding
1068            .left
1069            .saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
1070        let right = self
1071            .padding
1072            .right
1073            .saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
1074        (left, right)
1075    }
1076
1077    /// Calculate the top, and bottom space that the [`Block`] will take up.
1078    ///
1079    /// Takes the [`Padding`], [`TitlePosition`], and the [`Borders`] that are selected into
1080    /// account when calculating the result.
1081    pub(crate) fn vertical_space(&self) -> (u16, u16) {
1082        let has_top =
1083            self.borders.contains(Borders::TOP) || self.has_title_at_position(TitlePosition::Top);
1084        let top = self.padding.top + u16::from(has_top);
1085        let has_bottom = self.borders.contains(Borders::BOTTOM)
1086            || self.has_title_at_position(TitlePosition::Bottom);
1087        let bottom = self.padding.bottom + u16::from(has_bottom);
1088        (top, bottom)
1089    }
1090}
1091
1092/// An extension trait for [`Block`] that provides some convenience methods.
1093///
1094/// This is implemented for [`Option<Block>`](Option) to simplify the common case of having a
1095/// widget with an optional block.
1096pub trait BlockExt {
1097    /// Return the inner area of the block if it is `Some`. Otherwise, returns `area`.
1098    ///
1099    /// This is a useful convenience method for widgets that have an `Option<Block>` field
1100    fn inner_if_some(&self, area: Rect) -> Rect;
1101}
1102
1103impl BlockExt for Option<Block<'_>> {
1104    fn inner_if_some(&self, area: Rect) -> Rect {
1105        self.as_ref().map_or(area, |block| block.inner(area))
1106    }
1107}
1108
1109impl Styled for Block<'_> {
1110    type Item = Self;
1111
1112    fn style(&self) -> Style {
1113        self.style
1114    }
1115
1116    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
1117        self.style(style)
1118    }
1119}
1120
1121#[cfg(test)]
1122mod tests {
1123    use alloc::{format, vec};
1124
1125    use itertools::iproduct;
1126    use ratatui_core::layout::Offset;
1127    use ratatui_core::style::{Color, Modifier, Stylize};
1128    use rstest::rstest;
1129    use strum::ParseError;
1130
1131    use super::*;
1132
1133    #[test]
1134    fn create_with_all_borders() {
1135        let block = Block::bordered();
1136        assert_eq!(block.borders, Borders::all());
1137    }
1138
1139    #[rstest]
1140    #[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
1141    #[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
1142    #[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
1143    #[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1144    #[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
1145    #[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
1146    #[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
1147    #[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1148    #[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
1149    #[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
1150    #[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
1151    #[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1152    #[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
1153    #[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
1154    #[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
1155    #[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1156    #[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
1157    #[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
1158    #[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
1159    #[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
1160    #[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
1161    #[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
1162    fn inner_takes_into_account_the_borders(
1163        #[case] borders: Borders,
1164        #[case] area: Rect,
1165        #[case] expected: Rect,
1166    ) {
1167        let block = Block::new().borders(borders);
1168        assert_eq!(block.inner(area), expected);
1169    }
1170
1171    #[rstest]
1172    #[case::left(Alignment::Left)]
1173    #[case::center(Alignment::Center)]
1174    #[case::right(Alignment::Right)]
1175    fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
1176        let area = Rect::new(0, 0, 0, 1);
1177        let expected = Rect::new(0, 1, 0, 0);
1178
1179        let block = Block::new().title(Line::from("Test").alignment(alignment));
1180        assert_eq!(block.inner(area), expected);
1181    }
1182
1183    #[rstest]
1184    #[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
1185    #[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
1186    #[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
1187    #[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
1188    fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
1189        let area = Rect::new(0, 0, 0, 2);
1190        assert_eq!(block.inner(area), expected);
1191    }
1192
1193    #[test]
1194    fn has_title_at_position_takes_into_account_all_positioning_declarations() {
1195        let block = Block::new();
1196        assert!(!block.has_title_at_position(TitlePosition::Top));
1197        assert!(!block.has_title_at_position(TitlePosition::Bottom));
1198
1199        let block = Block::new().title_top("test");
1200        assert!(block.has_title_at_position(TitlePosition::Top));
1201        assert!(!block.has_title_at_position(TitlePosition::Bottom));
1202
1203        let block = Block::new().title_bottom("test");
1204        assert!(!block.has_title_at_position(TitlePosition::Top));
1205        assert!(block.has_title_at_position(TitlePosition::Bottom));
1206
1207        let block = Block::new().title_top("test").title_bottom("test");
1208        assert!(block.has_title_at_position(TitlePosition::Top));
1209        assert!(block.has_title_at_position(TitlePosition::Bottom));
1210    }
1211
1212    #[rstest]
1213    #[case::none(Borders::NONE, (0, 0))]
1214    #[case::top(Borders::TOP, (1, 0))]
1215    #[case::right(Borders::RIGHT, (0, 0))]
1216    #[case::bottom(Borders::BOTTOM, (0, 1))]
1217    #[case::left(Borders::LEFT, (0, 0))]
1218    #[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
1219    #[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
1220    #[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
1221    #[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
1222    #[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
1223    #[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
1224    fn vertical_space_takes_into_account_borders(
1225        #[case] borders: Borders,
1226        #[case] vertical_space: (u16, u16),
1227    ) {
1228        let block = Block::new().borders(borders);
1229        assert_eq!(block.vertical_space(), vertical_space);
1230    }
1231
1232    #[rstest]
1233    #[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
1234    #[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
1235    #[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
1236    #[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
1237    #[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
1238    #[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
1239    #[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
1240    fn vertical_space_takes_into_account_padding(
1241        #[case] borders: Borders,
1242        #[case] padding: Padding,
1243        #[case] vertical_space: (u16, u16),
1244    ) {
1245        let block = Block::new().borders(borders).padding(padding);
1246        assert_eq!(block.vertical_space(), vertical_space);
1247    }
1248
1249    #[test]
1250    fn vertical_space_takes_into_account_titles() {
1251        let block = Block::new().title_top("Test");
1252        assert_eq!(block.vertical_space(), (1, 0));
1253
1254        let block = Block::new().title_bottom("Test");
1255        assert_eq!(block.vertical_space(), (0, 1));
1256    }
1257
1258    #[rstest]
1259    #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Top, (1, 0))]
1260    #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Top, (1, 0))]
1261    #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Top, (1, 1))]
1262    #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Top, (1, 0))]
1263    #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Bottom, (1, 1))]
1264    #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Bottom, (0, 1))]
1265    #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Bottom, (0, 1))]
1266    #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Bottom, (0, 1))]
1267    fn vertical_space_takes_into_account_borders_and_title(
1268        #[case] block: Block,
1269        #[case] borders: Borders,
1270        #[case] pos: TitlePosition,
1271        #[case] vertical_space: (u16, u16),
1272    ) {
1273        let block = block.borders(borders).title_position(pos).title("Test");
1274        assert_eq!(block.vertical_space(), vertical_space);
1275    }
1276
1277    #[test]
1278    fn horizontal_space_takes_into_account_borders() {
1279        let block = Block::bordered();
1280        assert_eq!(block.horizontal_space(), (1, 1));
1281
1282        let block = Block::new().borders(Borders::LEFT);
1283        assert_eq!(block.horizontal_space(), (1, 0));
1284
1285        let block = Block::new().borders(Borders::RIGHT);
1286        assert_eq!(block.horizontal_space(), (0, 1));
1287    }
1288
1289    #[test]
1290    fn horizontal_space_takes_into_account_padding() {
1291        let block = Block::new().padding(Padding::new(1, 1, 100, 100));
1292        assert_eq!(block.horizontal_space(), (1, 1));
1293
1294        let block = Block::new().padding(Padding::new(3, 5, 0, 0));
1295        assert_eq!(block.horizontal_space(), (3, 5));
1296
1297        let block = Block::new().padding(Padding::new(0, 1, 100, 100));
1298        assert_eq!(block.horizontal_space(), (0, 1));
1299
1300        let block = Block::new().padding(Padding::new(1, 0, 100, 100));
1301        assert_eq!(block.horizontal_space(), (1, 0));
1302    }
1303
1304    #[rstest]
1305    #[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
1306    #[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
1307    #[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
1308    #[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
1309    #[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
1310    #[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
1311    #[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
1312    #[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
1313    #[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
1314    fn horizontal_space_takes_into_account_borders_and_padding(
1315        #[case] block: Block,
1316        #[case] padding: Padding,
1317        #[case] horizontal_space: (u16, u16),
1318    ) {
1319        let block = block.padding(padding);
1320        assert_eq!(block.horizontal_space(), horizontal_space);
1321    }
1322
1323    #[test]
1324    const fn border_type_can_be_const() {
1325        const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
1326    }
1327
1328    #[test]
1329    fn block_new() {
1330        assert_eq!(
1331            Block::new(),
1332            Block {
1333                titles: Vec::new(),
1334                titles_style: Style::new(),
1335                titles_alignment: Alignment::Left,
1336                titles_position: TitlePosition::Top,
1337                borders: Borders::NONE,
1338                border_style: Style::new(),
1339                border_set: BorderType::Plain.to_border_set(),
1340                style: Style::new(),
1341                padding: Padding::ZERO,
1342                merge_borders: MergeStrategy::Replace,
1343            }
1344        );
1345    }
1346
1347    #[test]
1348    const fn block_can_be_const() {
1349        const _DEFAULT_STYLE: Style = Style::new();
1350        const _DEFAULT_PADDING: Padding = Padding::uniform(1);
1351        const _DEFAULT_BLOCK: Block = Block::bordered()
1352            // the following methods are no longer const because they use Into<Style>
1353            // .style(_DEFAULT_STYLE)           // no longer const
1354            // .border_style(_DEFAULT_STYLE)    // no longer const
1355            // .title_style(_DEFAULT_STYLE)     // no longer const
1356            .title_alignment(Alignment::Left)
1357            .title_position(TitlePosition::Top)
1358            .padding(_DEFAULT_PADDING);
1359    }
1360
1361    /// Ensure Style from/into works the way a user would use it.
1362    #[test]
1363    fn style_into_works_from_user_view() {
1364        // nominal style
1365        let block = Block::new().style(Style::new().red());
1366        assert_eq!(block.style, Style::new().red());
1367
1368        // auto-convert from Color
1369        let block = Block::new().style(Color::Red);
1370        assert_eq!(block.style, Style::new().red());
1371
1372        // auto-convert from (Color, Color)
1373        let block = Block::new().style((Color::Red, Color::Blue));
1374        assert_eq!(block.style, Style::new().red().on_blue());
1375
1376        // auto-convert from Modifier
1377        let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
1378        assert_eq!(block.style, Style::new().bold().italic());
1379
1380        // auto-convert from (Modifier, Modifier)
1381        let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
1382        assert_eq!(block.style, Style::new().bold().italic().not_dim());
1383
1384        // auto-convert from (Color, Modifier)
1385        let block = Block::new().style((Color::Red, Modifier::BOLD));
1386        assert_eq!(block.style, Style::new().red().bold());
1387
1388        // auto-convert from (Color, Color, Modifier)
1389        let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
1390        assert_eq!(block.style, Style::new().red().on_blue().bold());
1391
1392        // auto-convert from (Color, Color, Modifier, Modifier)
1393        let block = Block::new().style((
1394            Color::Red,
1395            Color::Blue,
1396            Modifier::BOLD | Modifier::ITALIC,
1397            Modifier::DIM,
1398        ));
1399        assert_eq!(
1400            block.style,
1401            Style::new().red().on_blue().bold().italic().not_dim()
1402        );
1403    }
1404
1405    #[test]
1406    fn can_be_stylized() {
1407        let block = Block::new().black().on_white().bold().not_dim();
1408        assert_eq!(
1409            block.style,
1410            Style::default()
1411                .fg(Color::Black)
1412                .bg(Color::White)
1413                .add_modifier(Modifier::BOLD)
1414                .remove_modifier(Modifier::DIM)
1415        );
1416    }
1417
1418    #[test]
1419    fn title_top_bottom() {
1420        let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1421        Block::bordered()
1422            .title_top(Line::raw("A").left_aligned())
1423            .title_top(Line::raw("B").centered())
1424            .title_top(Line::raw("C").right_aligned())
1425            .title_bottom(Line::raw("D").left_aligned())
1426            .title_bottom(Line::raw("E").centered())
1427            .title_bottom(Line::raw("F").right_aligned())
1428            .render(buffer.area, &mut buffer);
1429        #[rustfmt::skip]
1430        let expected = Buffer::with_lines([
1431            "┌A───B───C┐",
1432            "│         │",
1433            "└D───E───F┘",
1434        ]);
1435        assert_eq!(buffer, expected);
1436    }
1437
1438    #[test]
1439    fn title_alignment() {
1440        let tests = vec![
1441            (Alignment::Left, "test    "),
1442            (Alignment::Center, "  test  "),
1443            (Alignment::Right, "    test"),
1444        ];
1445        for (alignment, expected) in tests {
1446            let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1447            Block::new()
1448                .title_alignment(alignment)
1449                .title("test")
1450                .render(buffer.area, &mut buffer);
1451            assert_eq!(buffer, Buffer::with_lines([expected]));
1452        }
1453    }
1454
1455    #[test]
1456    fn title_alignment_overrides_block_title_alignment() {
1457        let tests = vec![
1458            (Alignment::Right, Alignment::Left, "test    "),
1459            (Alignment::Left, Alignment::Center, "  test  "),
1460            (Alignment::Center, Alignment::Right, "    test"),
1461        ];
1462        for (block_title_alignment, alignment, expected) in tests {
1463            let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1464            Block::new()
1465                .title_alignment(block_title_alignment)
1466                .title(Line::from("test").alignment(alignment))
1467                .render(buffer.area, &mut buffer);
1468            assert_eq!(buffer, Buffer::with_lines([expected]));
1469        }
1470    }
1471
1472    /// This is a regression test for bug <https://github.com/ratatui/ratatui/issues/929>
1473    #[test]
1474    fn render_right_aligned_empty_title() {
1475        let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
1476        Block::new()
1477            .title_alignment(Alignment::Right)
1478            .title("")
1479            .render(buffer.area, &mut buffer);
1480        assert_eq!(buffer, Buffer::with_lines(["               "; 3]));
1481    }
1482
1483    #[test]
1484    fn title_position() {
1485        let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
1486        Block::new()
1487            .title_position(TitlePosition::Bottom)
1488            .title("test")
1489            .render(buffer.area, &mut buffer);
1490        assert_eq!(buffer, Buffer::with_lines(["    ", "test"]));
1491    }
1492
1493    #[test]
1494    fn title_content_style() {
1495        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1496            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1497            Block::new()
1498                .title_alignment(alignment)
1499                .title("test".yellow())
1500                .render(buffer.area, &mut buffer);
1501            assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1502        }
1503    }
1504
1505    #[test]
1506    fn block_title_style() {
1507        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1508            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1509            Block::new()
1510                .title_alignment(alignment)
1511                .title_style(Style::new().yellow())
1512                .title("test")
1513                .render(buffer.area, &mut buffer);
1514            assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1515        }
1516    }
1517
1518    #[test]
1519    fn title_style_overrides_block_title_style() {
1520        for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1521            let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1522            Block::new()
1523                .title_alignment(alignment)
1524                .title_style(Style::new().green().on_red())
1525                .title("test".yellow())
1526                .render(buffer.area, &mut buffer);
1527            assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
1528        }
1529    }
1530
1531    #[test]
1532    fn title_border_style() {
1533        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1534        Block::bordered()
1535            .title("test")
1536            .border_style(Style::new().yellow())
1537            .render(buffer.area, &mut buffer);
1538        #[rustfmt::skip]
1539        let mut expected = Buffer::with_lines([
1540            "┌test────┐",
1541            "│        │",
1542            "└────────┘",
1543        ]);
1544        expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
1545        expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
1546        assert_eq!(buffer, expected);
1547    }
1548
1549    #[test]
1550    fn border_type_to_string() {
1551        assert_eq!(format!("{}", BorderType::Plain), "Plain");
1552        assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
1553        assert_eq!(format!("{}", BorderType::Double), "Double");
1554        assert_eq!(format!("{}", BorderType::Thick), "Thick");
1555        assert_eq!(
1556            format!("{}", BorderType::LightDoubleDashed),
1557            "LightDoubleDashed"
1558        );
1559        assert_eq!(
1560            format!("{}", BorderType::HeavyDoubleDashed),
1561            "HeavyDoubleDashed"
1562        );
1563        assert_eq!(
1564            format!("{}", BorderType::LightTripleDashed),
1565            "LightTripleDashed"
1566        );
1567        assert_eq!(
1568            format!("{}", BorderType::HeavyTripleDashed),
1569            "HeavyTripleDashed"
1570        );
1571        assert_eq!(
1572            format!("{}", BorderType::LightQuadrupleDashed),
1573            "LightQuadrupleDashed"
1574        );
1575        assert_eq!(
1576            format!("{}", BorderType::HeavyQuadrupleDashed),
1577            "HeavyQuadrupleDashed"
1578        );
1579    }
1580
1581    #[test]
1582    fn border_type_from_str() {
1583        assert_eq!("Plain".parse(), Ok(BorderType::Plain));
1584        assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
1585        assert_eq!("Double".parse(), Ok(BorderType::Double));
1586        assert_eq!("Thick".parse(), Ok(BorderType::Thick));
1587        assert_eq!(
1588            "LightDoubleDashed".parse(),
1589            Ok(BorderType::LightDoubleDashed)
1590        );
1591        assert_eq!(
1592            "HeavyDoubleDashed".parse(),
1593            Ok(BorderType::HeavyDoubleDashed)
1594        );
1595        assert_eq!(
1596            "LightTripleDashed".parse(),
1597            Ok(BorderType::LightTripleDashed)
1598        );
1599        assert_eq!(
1600            "HeavyTripleDashed".parse(),
1601            Ok(BorderType::HeavyTripleDashed)
1602        );
1603        assert_eq!(
1604            "LightQuadrupleDashed".parse(),
1605            Ok(BorderType::LightQuadrupleDashed)
1606        );
1607        assert_eq!(
1608            "HeavyQuadrupleDashed".parse(),
1609            Ok(BorderType::HeavyQuadrupleDashed)
1610        );
1611        assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
1612    }
1613
1614    #[test]
1615    fn render_plain_border() {
1616        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1617        Block::bordered()
1618            .border_type(BorderType::Plain)
1619            .render(buffer.area, &mut buffer);
1620        #[rustfmt::skip]
1621        let expected = Buffer::with_lines([
1622            "┌────────┐",
1623            "│        │",
1624            "└────────┘",
1625        ]);
1626        assert_eq!(buffer, expected);
1627    }
1628
1629    #[test]
1630    fn render_rounded_border() {
1631        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1632        Block::bordered()
1633            .border_type(BorderType::Rounded)
1634            .render(buffer.area, &mut buffer);
1635        #[rustfmt::skip]
1636        let expected = Buffer::with_lines([
1637            "╭────────╮",
1638            "│        │",
1639            "╰────────╯",
1640        ]);
1641        assert_eq!(buffer, expected);
1642    }
1643
1644    #[test]
1645    fn render_double_border() {
1646        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1647        Block::bordered()
1648            .border_type(BorderType::Double)
1649            .render(buffer.area, &mut buffer);
1650        #[rustfmt::skip]
1651        let expected = Buffer::with_lines([
1652            "╔════════╗",
1653            "║        ║",
1654            "╚════════╝",
1655        ]);
1656        assert_eq!(buffer, expected);
1657    }
1658
1659    #[test]
1660    fn render_quadrant_inside() {
1661        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1662        Block::bordered()
1663            .border_type(BorderType::QuadrantInside)
1664            .render(buffer.area, &mut buffer);
1665        #[rustfmt::skip]
1666        let expected = Buffer::with_lines([
1667            "▗▄▄▄▄▄▄▄▄▖",
1668            "▐        ▌",
1669            "▝▀▀▀▀▀▀▀▀▘",
1670        ]);
1671        assert_eq!(buffer, expected);
1672    }
1673
1674    #[test]
1675    fn render_border_quadrant_outside() {
1676        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1677        Block::bordered()
1678            .border_type(BorderType::QuadrantOutside)
1679            .render(buffer.area, &mut buffer);
1680        #[rustfmt::skip]
1681        let expected = Buffer::with_lines([
1682            "▛▀▀▀▀▀▀▀▀▜",
1683            "▌        ▐",
1684            "▙▄▄▄▄▄▄▄▄▟",
1685        ]);
1686        assert_eq!(buffer, expected);
1687    }
1688
1689    #[test]
1690    fn render_solid_border() {
1691        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1692        Block::bordered()
1693            .border_type(BorderType::Thick)
1694            .render(buffer.area, &mut buffer);
1695        #[rustfmt::skip]
1696        let expected = Buffer::with_lines([
1697            "┏━━━━━━━━┓",
1698            "┃        ┃",
1699            "┗━━━━━━━━┛",
1700        ]);
1701        assert_eq!(buffer, expected);
1702    }
1703
1704    #[test]
1705    fn render_light_double_dashed_border() {
1706        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1707        Block::bordered()
1708            .border_type(BorderType::LightDoubleDashed)
1709            .render(buffer.area, &mut buffer);
1710        #[rustfmt::skip]
1711        let expected = Buffer::with_lines([
1712            "┌╌╌╌╌╌╌╌╌┐",
1713            "╎        ╎",
1714            "└╌╌╌╌╌╌╌╌┘",
1715        ]);
1716        assert_eq!(buffer, expected);
1717    }
1718
1719    #[test]
1720    fn render_heavy_double_dashed_border() {
1721        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1722        Block::bordered()
1723            .border_type(BorderType::HeavyDoubleDashed)
1724            .render(buffer.area, &mut buffer);
1725        #[rustfmt::skip]
1726        let expected = Buffer::with_lines([
1727            "┏╍╍╍╍╍╍╍╍┓",
1728            "╏        ╏",
1729            "┗╍╍╍╍╍╍╍╍┛",
1730        ]);
1731        assert_eq!(buffer, expected);
1732    }
1733
1734    #[test]
1735    fn render_light_triple_dashed_border() {
1736        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1737        Block::bordered()
1738            .border_type(BorderType::LightTripleDashed)
1739            .render(buffer.area, &mut buffer);
1740        #[rustfmt::skip]
1741        let expected = Buffer::with_lines([
1742            "┌┄┄┄┄┄┄┄┄┐",
1743            "┆        ┆",
1744            "└┄┄┄┄┄┄┄┄┘",
1745        ]);
1746        assert_eq!(buffer, expected);
1747    }
1748
1749    #[test]
1750    fn render_heavy_triple_dashed_border() {
1751        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1752        Block::bordered()
1753            .border_type(BorderType::HeavyTripleDashed)
1754            .render(buffer.area, &mut buffer);
1755        #[rustfmt::skip]
1756        let expected = Buffer::with_lines([
1757            "┏┅┅┅┅┅┅┅┅┓",
1758            "┇        ┇",
1759            "┗┅┅┅┅┅┅┅┅┛",
1760        ]);
1761        assert_eq!(buffer, expected);
1762    }
1763
1764    #[test]
1765    fn render_light_quadruple_dashed_border() {
1766        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1767        Block::bordered()
1768            .border_type(BorderType::LightQuadrupleDashed)
1769            .render(buffer.area, &mut buffer);
1770        #[rustfmt::skip]
1771        let expected = Buffer::with_lines([
1772            "┌┈┈┈┈┈┈┈┈┐",
1773            "┊        ┊",
1774            "└┈┈┈┈┈┈┈┈┘",
1775        ]);
1776        assert_eq!(buffer, expected);
1777    }
1778
1779    #[test]
1780    fn render_heavy_quadruple_dashed_border() {
1781        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1782        Block::bordered()
1783            .border_type(BorderType::HeavyQuadrupleDashed)
1784            .render(buffer.area, &mut buffer);
1785        #[rustfmt::skip]
1786        let expected = Buffer::with_lines([
1787            "┏┉┉┉┉┉┉┉┉┓",
1788            "┋        ┋",
1789            "┗┉┉┉┉┉┉┉┉┛",
1790        ]);
1791        assert_eq!(buffer, expected);
1792    }
1793
1794    #[test]
1795    fn render_custom_border_set() {
1796        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1797        Block::bordered()
1798            .border_set(border::Set {
1799                top_left: "1",
1800                top_right: "2",
1801                bottom_left: "3",
1802                bottom_right: "4",
1803                vertical_left: "L",
1804                vertical_right: "R",
1805                horizontal_top: "T",
1806                horizontal_bottom: "B",
1807            })
1808            .render(buffer.area, &mut buffer);
1809        #[rustfmt::skip]
1810        let expected = Buffer::with_lines([
1811            "1TTTTTTTT2",
1812            "L        R",
1813            "3BBBBBBBB4",
1814        ]);
1815        assert_eq!(buffer, expected);
1816    }
1817
1818    #[rstest]
1819    #[case::replace(MergeStrategy::Replace)]
1820    #[case::exact(MergeStrategy::Exact)]
1821    #[case::fuzzy(MergeStrategy::Fuzzy)]
1822    fn render_partial_borders(#[case] strategy: MergeStrategy) {
1823        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1824        Block::new()
1825            .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
1826            .merge_borders(strategy)
1827            .render(buffer.area, &mut buffer);
1828        #[rustfmt::skip]
1829        let expected = Buffer::with_lines([
1830            "┌────────┐",
1831            "│        │",
1832            "└────────┘",
1833        ]);
1834        assert_eq!(buffer, expected);
1835
1836        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1837        Block::new()
1838            .borders(Borders::TOP | Borders::LEFT)
1839            .merge_borders(strategy)
1840            .render(buffer.area, &mut buffer);
1841        #[rustfmt::skip]
1842        let expected = Buffer::with_lines([
1843            "┌─────────",
1844            "│         ",
1845            "│         ",
1846        ]);
1847        assert_eq!(buffer, expected);
1848
1849        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1850        Block::new()
1851            .borders(Borders::TOP | Borders::RIGHT)
1852            .merge_borders(strategy)
1853            .render(buffer.area, &mut buffer);
1854        #[rustfmt::skip]
1855        let expected = Buffer::with_lines([
1856            "─────────┐",
1857            "         │",
1858            "         │",
1859        ]);
1860        assert_eq!(buffer, expected);
1861
1862        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1863        Block::new()
1864            .borders(Borders::BOTTOM | Borders::LEFT)
1865            .merge_borders(strategy)
1866            .render(buffer.area, &mut buffer);
1867        #[rustfmt::skip]
1868        let expected = Buffer::with_lines([
1869            "│         ",
1870            "│         ",
1871            "└─────────",
1872        ]);
1873        assert_eq!(buffer, expected);
1874
1875        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1876        Block::new()
1877            .borders(Borders::BOTTOM | Borders::RIGHT)
1878            .merge_borders(strategy)
1879            .render(buffer.area, &mut buffer);
1880        #[rustfmt::skip]
1881        let expected = Buffer::with_lines([
1882            "         │",
1883            "         │",
1884            "─────────┘",
1885        ]);
1886        assert_eq!(buffer, expected);
1887
1888        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1889        Block::new()
1890            .borders(Borders::TOP | Borders::BOTTOM)
1891            .merge_borders(strategy)
1892            .render(buffer.area, &mut buffer);
1893        #[rustfmt::skip]
1894        let expected = Buffer::with_lines([
1895            "──────────",
1896            "          ",
1897            "──────────",
1898        ]);
1899        assert_eq!(buffer, expected);
1900
1901        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1902        Block::new()
1903            .borders(Borders::LEFT | Borders::RIGHT)
1904            .merge_borders(strategy)
1905            .render(buffer.area, &mut buffer);
1906        #[rustfmt::skip]
1907        let expected = Buffer::with_lines([
1908            "│        │",
1909            "│        │",
1910            "│        │",
1911        ]);
1912        assert_eq!(buffer, expected);
1913    }
1914
1915    /// Renders a series of blocks with all the possible border types and merges them according to
1916    /// the specified strategy. The resulting buffer is compared against the expected output for
1917    /// each merge strategy.
1918    ///
1919    /// At some point, it might be convenient to replace the manual `include_str!` calls with
1920    /// [insta](https://crates.io/crates/insta)
1921    #[rstest]
1922    #[case::replace(MergeStrategy::Replace, include_str!("../tests/block/merge_replace.txt"))]
1923    #[case::exact(MergeStrategy::Exact, include_str!("../tests/block/merge_exact.txt"))]
1924    #[case::fuzzy(MergeStrategy::Fuzzy, include_str!("../tests/block/merge_fuzzy.txt"))]
1925    fn render_merged_borders(#[case] strategy: MergeStrategy, #[case] expected: &'static str) {
1926        let border_types = [
1927            BorderType::Plain,
1928            BorderType::Rounded,
1929            BorderType::Thick,
1930            BorderType::Double,
1931            BorderType::LightDoubleDashed,
1932            BorderType::HeavyDoubleDashed,
1933            BorderType::LightTripleDashed,
1934            BorderType::HeavyTripleDashed,
1935            BorderType::LightQuadrupleDashed,
1936            BorderType::HeavyQuadrupleDashed,
1937        ];
1938        let rects = [
1939            // touching at corners
1940            (Rect::new(0, 0, 5, 5), Rect::new(4, 4, 5, 5)),
1941            // overlapping
1942            (Rect::new(10, 0, 5, 5), Rect::new(12, 2, 5, 5)),
1943            // touching vertical edges
1944            (Rect::new(18, 0, 5, 5), Rect::new(22, 0, 5, 5)),
1945            // touching horizontal edges
1946            (Rect::new(28, 0, 5, 5), Rect::new(28, 4, 5, 5)),
1947        ];
1948
1949        let mut buffer = Buffer::empty(Rect::new(0, 0, 43, 1000));
1950
1951        let mut offset = Offset::ZERO;
1952        for (border_type_1, border_type_2) in iproduct!(border_types, border_types) {
1953            let title = format!("{border_type_1} + {border_type_2}");
1954            let title_area = Rect::new(0, 0, 43, 1) + offset;
1955            title.render(title_area, &mut buffer);
1956            offset.y += 1;
1957            for (rect_1, rect_2) in rects {
1958                Block::bordered()
1959                    .border_type(border_type_1)
1960                    .merge_borders(strategy)
1961                    .render(rect_1 + offset, &mut buffer);
1962                Block::bordered()
1963                    .border_type(border_type_2)
1964                    .merge_borders(strategy)
1965                    .render(rect_2 + offset, &mut buffer);
1966            }
1967            offset.y += 9;
1968        }
1969        pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
1970    }
1971
1972    #[rstest]
1973    #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
1974            "┏block top━━┓",
1975            "┃           ┃",
1976            "┗━━━━━━━━━━━┛",
1977            "│           │",
1978            "└───────────┘",
1979        ])
1980    )]
1981    #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
1982            "┏block top━━┓",
1983            "┃           ┃",
1984            "┡block btm━━┩",
1985            "│           │",
1986            "└───────────┘",
1987        ])
1988    )]
1989    #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
1990            "┏block top━━┓",
1991            "┃           ┃",
1992            "┡block btm━━┩",
1993            "│           │",
1994            "└───────────┘",
1995        ])
1996    )]
1997    fn merged_titles_bottom_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
1998        let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
1999        Block::bordered()
2000            .title("block btm")
2001            .render(Rect::new(0, 2, 13, 3), &mut buffer);
2002        Block::bordered()
2003            .title("block top")
2004            .border_type(BorderType::Thick)
2005            .merge_borders(strategy)
2006            .render(Rect::new(0, 0, 13, 3), &mut buffer);
2007        assert_eq!(buffer, expected);
2008    }
2009
2010    #[rstest]
2011    #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
2012            "┏block top━━┓",
2013            "┃           ┃",
2014            "┌block btm──┐",
2015            "│           │",
2016            "└───────────┘",
2017        ])
2018    )]
2019    #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
2020            "┏block top━━┓",
2021            "┃           ┃",
2022            "┞block btm──┦",
2023            "│           │",
2024            "└───────────┘",
2025        ])
2026    )]
2027    #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
2028            "┏block top━━┓",
2029            "┃           ┃",
2030            "┞block btm──┦",
2031            "│           │",
2032            "└───────────┘",
2033        ])
2034    )]
2035    fn merged_titles_top_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
2036        let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
2037        Block::bordered()
2038            .title("block top")
2039            .border_type(BorderType::Thick)
2040            .render(Rect::new(0, 0, 13, 3), &mut buffer);
2041        Block::bordered()
2042            .title("block btm")
2043            .merge_borders(strategy)
2044            .render(Rect::new(0, 2, 13, 3), &mut buffer);
2045        assert_eq!(buffer, expected);
2046    }
2047
2048    #[test]
2049    fn left_titles() {
2050        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2051        Block::new()
2052            .title("L12")
2053            .title("L34")
2054            .render(buffer.area, &mut buffer);
2055        assert_eq!(buffer, Buffer::with_lines(["L12 L34   "]));
2056    }
2057
2058    #[test]
2059    fn left_titles_truncated() {
2060        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2061        Block::new()
2062            .title("L12345")
2063            .title("L67890")
2064            .render(buffer.area, &mut buffer);
2065        assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
2066    }
2067
2068    #[test]
2069    fn center_titles() {
2070        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2071        Block::new()
2072            .title(Line::from("C12").centered())
2073            .title(Line::from("C34").centered())
2074            .render(buffer.area, &mut buffer);
2075        assert_eq!(buffer, Buffer::with_lines([" C12 C34  "]));
2076    }
2077
2078    #[test]
2079    fn center_titles_truncated() {
2080        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2081        Block::new()
2082            .title(Line::from("C12345").centered())
2083            .title(Line::from("C67890").centered())
2084            .render(buffer.area, &mut buffer);
2085        assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
2086    }
2087
2088    #[test]
2089    fn right_titles() {
2090        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2091        Block::new()
2092            .title(Line::from("R12").right_aligned())
2093            .title(Line::from("R34").right_aligned())
2094            .render(buffer.area, &mut buffer);
2095        assert_eq!(buffer, Buffer::with_lines(["   R12 R34"]));
2096    }
2097
2098    #[test]
2099    fn right_titles_truncated() {
2100        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2101        Block::new()
2102            .title(Line::from("R12345").right_aligned())
2103            .title(Line::from("R67890").right_aligned())
2104            .render(buffer.area, &mut buffer);
2105        assert_eq!(buffer, Buffer::with_lines(["345 R67890"]));
2106    }
2107
2108    #[test]
2109    fn center_title_truncates_left_title() {
2110        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2111        Block::new()
2112            .title("L1234")
2113            .title(Line::from("C5678").centered())
2114            .render(buffer.area, &mut buffer);
2115        assert_eq!(buffer, Buffer::with_lines(["L1C5678   "]));
2116    }
2117
2118    #[test]
2119    fn right_title_truncates_left_title() {
2120        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2121        Block::new()
2122            .title("L12345")
2123            .title(Line::from("R67890").right_aligned())
2124            .render(buffer.area, &mut buffer);
2125        assert_eq!(buffer, Buffer::with_lines(["L123R67890"]));
2126    }
2127
2128    #[test]
2129    fn right_title_truncates_center_title() {
2130        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2131        Block::new()
2132            .title(Line::from("C12345").centered())
2133            .title(Line::from("R67890").right_aligned())
2134            .render(buffer.area, &mut buffer);
2135        assert_eq!(buffer, Buffer::with_lines(["  C1R67890"]));
2136    }
2137
2138    #[test]
2139    fn render_in_minimal_buffer() {
2140        let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2141        // This should not panic, even if the buffer is too small to render the block.
2142        Block::bordered()
2143            .title("I'm too big for this buffer")
2144            .padding(Padding::uniform(10))
2145            .render(buffer.area, &mut buffer);
2146        assert_eq!(buffer, Buffer::with_lines(["┌"]));
2147    }
2148
2149    #[test]
2150    fn render_in_zero_size_buffer() {
2151        let mut buffer = Buffer::empty(Rect::ZERO);
2152        // This should not panic, even if the buffer has zero size.
2153        Block::bordered()
2154            .title("I'm too big for this buffer")
2155            .padding(Padding::uniform(10))
2156            .render(buffer.area, &mut buffer);
2157    }
2158}