Skip to main content

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