chore: remove block::Title (#1926)

The title alignment is better expressed in the `Line` as this fits more
coherently with the rest of the library.

BREAKING CHANGES:
- `widgets::block` is no longer exported
- `widgets::block::Title` no longer exists
- `widgets::block::Position` is now `widgets::TitlePosition`
- `Block::title()` now accepts `Into::<Line>` instead of `Into<Title>`
- `BlockExt` is now exported at widgets::`BlockExt`

Closes: https://github.com/ratatui/ratatui/issues/738
This commit is contained in:
Josh McKinney
2025-06-25 15:36:50 -07:00
committed by GitHub
parent 7bc78bca1b
commit 4c86513790
6 changed files with 139 additions and 326 deletions

View File

@@ -11,6 +11,7 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:
- [v0.30.0 Unreleased](#v0300-unreleased)
- `block::Title` no longer exists
- The `From` impls for backend types are now replaced with more specific traits
- `FrameExt` trait for `unstable-widget-ref` feature
- `List::highlight_symbol` now accepts `Into<Line>` instead of `&str`
@@ -19,10 +20,13 @@ This is a quick summary of the sections below:
- `Backend` now requires an associated `Error` type and `clear_region` method
- `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error`
- Disabling `default-features` will now disable layout cache, which can have a negative impact on performance
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal`
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache`
feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping
`Terminal`
- [v0.29.0](#v0290)
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer
const
- Removed public fields from `Rect` iterators
- `Line` now implements `From<Cow<str>`
- `Table::highlight_style` is now `Table::row_highlight_style`
@@ -85,6 +89,47 @@ This is a quick summary of the sections below:
## v0.30.0 Unreleased
### `block::Title` no longer exists ([#1926])
[#1926]: https://github.com/ratatui/ratatui/pull/1926
The title alignment is better expressed in the `Line` as this fits more coherently with the rest of
the library.
- `widgets::block` is no longer exported
- `widgets::block::Title` no longer exists
- `widgets::block::Position` is now `widgets::TitlePosition`
- `Block::title()` now accepts `Into::<Line>` instead of `Into<Title>`
- `BlockExt` is now exported at widgets::`BlockExt` instead of `widgets::block::BlockExt`
```diff
- use ratatui::widgets::{Block, block::{Title, Position}};
+ use ratatui::widgets::{Block, TitlePosition};
let block = Block::default()
- .title(Title::from("Hello"))
- .title(Title::from("Hello").position(Position::Bottom).alignment(Alignment::Center))
- .title_position(Position::Bottom);
+ .title(Line::from("Hello"))
+ .title_bottom(Line::from("Hello").centered());
+ .title_position(TitlePosition::Bottom);
- use ratatui::widgets::block::BlockExt;
+ use ratatui::widgets::BlockExt;
struct MyWidget {
block: Option<Block>,
}
impl Widget for &MyWidget {
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let area = self.block.inner_if_some();
// ...
}
}
```
### `Style` no longer implements `Styled` ([#1572])
[#1572]: https://github.com/ratatui/ratatui/pull/1572

View File

@@ -16,18 +16,17 @@ use ratatui_core::symbols::border;
use ratatui_core::symbols::merge::MergeStrategy;
use ratatui_core::text::Line;
use ratatui_core::widgets::Widget;
use strum::{Display, EnumString};
pub use self::padding::Padding;
pub use self::title::{Position, Title};
use crate::borders::{BorderType, Borders};
mod padding;
pub mod title;
/// Base widget to be used to display a box border around all other built-in widgets.
///
/// The borders can be configured with [`Block::borders`] and others. A block can have multiple
/// [`Title`] using [`Block::title`]. It can also be [styled](Block::style) and
/// titles using [`Block::title`]. It can also be [styled](Block::style) and
/// [padded](Block::padding).
///
/// You can call the title methods multiple times to add multiple titles. Each title will be
@@ -90,12 +89,9 @@ pub mod title;
///
/// You may also use multiple titles like in the following:
/// ```
/// use ratatui::widgets::Block;
/// use ratatui::widgets::block::{Position, Title};
/// use ratatui::widgets::{Block, TitlePosition};
///
/// Block::new()
/// .title("Title 1")
/// .title(Title::from("Title 2").position(Position::Bottom));
/// Block::new().title("Title 1").title_bottom("Title 2");
/// ```
///
/// You can also pass it as parameters of another widget so that the block surrounds them:
@@ -111,13 +107,13 @@ pub mod title;
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Block<'a> {
/// List of titles
titles: Vec<(Option<Position>, Line<'a>)>,
titles: Vec<(Option<TitlePosition>, Line<'a>)>,
/// The style to be patched to all titles of the block
titles_style: Style,
/// The default alignment of the titles that don't have one
titles_alignment: Alignment,
/// The default position of the titles that don't have one
titles_position: Position,
titles_position: TitlePosition,
/// Visible borders
borders: Borders,
/// Border style
@@ -133,6 +129,31 @@ pub struct Block<'a> {
merge_borders: MergeStrategy,
}
/// Defines the position of the title.
///
/// The title can be positioned on top or at the bottom of the block.
///
/// # Example
///
/// ```
/// use ratatui::widgets::{Block, TitlePosition};
///
/// Block::new()
/// .title_position(TitlePosition::Top)
/// .title("Top Title");
/// Block::new()
/// .title_position(TitlePosition::Bottom)
/// .title("Bottom Title");
/// ```
#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TitlePosition {
/// Position the title at the top of the block.
#[default]
Top,
/// Position the title at the bottom of the block.
Bottom,
}
impl<'a> Block<'a> {
/// Creates a new block with no [`Borders`] or [`Padding`].
pub const fn new() -> Self {
@@ -140,7 +161,7 @@ impl<'a> Block<'a> {
titles: Vec::new(),
titles_style: Style::new(),
titles_alignment: Alignment::Left,
titles_position: Position::Top,
titles_position: TitlePosition::Top,
borders: Borders::NONE,
border_style: Style::new(),
border_set: BorderType::Plain.to_border_set(),
@@ -172,7 +193,7 @@ impl<'a> Block<'a> {
/// position or alignment. When both centered and non-centered titles are rendered, the centered
/// space is calculated based on the full width of the block, rather than the leftover width.
///
/// You can provide any type that can be converted into [`Title`] including: strings, string
/// You can provide any type that can be converted into [`Line`] including: strings, string
/// slices (`&str`), borrowed strings (`Cow<str>`), [spans](ratatui_core::text::Span), or
/// vectors of [spans](ratatui_core::text::Span) (`Vec<Span>`).
///
@@ -205,12 +226,10 @@ impl<'a> Block<'a> {
/// use ratatui::widgets::{Block, Borders};
///
/// Block::new()
/// .title("Title") // By default in the top left corner
/// .title(Line::from("Left").left_aligned()) // also on the left
/// .title("Title")
/// .title(Line::from("Left").left_aligned())
/// .title(Line::from("Right").right_aligned())
/// .title(Line::from("Center").centered());
/// // Renders
/// // ┌Title─Left────Center─────────Right┐
/// ```
///
/// # See also
@@ -218,28 +237,21 @@ impl<'a> Block<'a> {
/// Titles attached to a block can have default behaviors. See
/// - [`Block::title_style`]
/// - [`Block::title_alignment`]
/// - [`Block::title_position`]
///
/// # Future improvements
/// # History
///
/// In a future release of Ratatui this method will be changed to accept `Into<Line>` instead of
/// `Into<Title>`. This allows us to remove the unnecessary `Title` struct and store the
/// position in the block itself. For more information see
/// <https://github.com/ratatui/ratatui/issues/738>.
/// In previous releases of Ratatui this method accepted `Into<Title>` instead of
/// [`Into<Line>`]. We found that storing the position in the block and the alignment in the
/// line better reflects the intended use of the block and its titles. See
/// <https://github.com/ratatui/ratatui/issues/738> for more information.
///
/// [Block example]: https://github.com/ratatui/ratatui/blob/main/examples/README.md#block
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title<T>(mut self, title: T) -> Self
where
T: Into<Title<'a>>,
T: Into<Line<'a>>,
{
let title = title.into();
let position = title.position;
let mut content = title.content;
if let Some(alignment) = title.alignment {
content = content.alignment(alignment);
}
self.titles.push((position, content));
self.titles.push((None, title.into()));
self
}
@@ -268,7 +280,7 @@ impl<'a> Block<'a> {
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let line = title.into();
self.titles.push((Some(Position::Top), line));
self.titles.push((Some(TitlePosition::Top), line));
self
}
@@ -297,7 +309,7 @@ impl<'a> Block<'a> {
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let line = title.into();
self.titles.push((Some(Position::Bottom), line));
self.titles.push((Some(TitlePosition::Bottom), line));
self
}
@@ -345,27 +357,23 @@ impl<'a> Block<'a> {
self
}
/// Sets the default [`Position`] for all block [titles](Title).
///
/// Titles that explicitly set a [`Position`] will ignore this.
/// Sets the default [`TitlePosition`] for all block titles.
///
/// # Example
///
/// This example positions all titles on the bottom except the "top" title which explicitly sets
/// [`Position::Top`].
/// [`TitlePosition::Top`].
/// ```
/// use ratatui::widgets::Block;
/// use ratatui::widgets::block::Position;
/// use ratatui::widgets::{Block, TitlePosition};
///
/// Block::new()
/// .title_position(Position::Bottom)
/// // This title won't be aligned in the center
/// .title_position(TitlePosition::Bottom)
/// .title_top("top")
/// .title("foo")
/// .title("bar");
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn title_position(mut self, position: Position) -> Self {
pub const fn title_position(mut self, position: TitlePosition) -> Self {
self.titles_position = position;
self
}
@@ -616,14 +624,15 @@ impl<'a> Block<'a> {
inner.x = inner.x.saturating_add(1).min(inner.right());
inner.width = inner.width.saturating_sub(1);
}
if self.borders.intersects(Borders::TOP) || self.has_title_at_position(Position::Top) {
if self.borders.intersects(Borders::TOP) || self.has_title_at_position(TitlePosition::Top) {
inner.y = inner.y.saturating_add(1).min(inner.bottom());
inner.height = inner.height.saturating_sub(1);
}
if self.borders.intersects(Borders::RIGHT) {
inner.width = inner.width.saturating_sub(1);
}
if self.borders.intersects(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom)
if self.borders.intersects(Borders::BOTTOM)
|| self.has_title_at_position(TitlePosition::Bottom)
{
inner.height = inner.height.saturating_sub(1);
}
@@ -641,7 +650,7 @@ impl<'a> Block<'a> {
inner
}
fn has_title_at_position(&self, position: Position) -> bool {
fn has_title_at_position(&self, position: TitlePosition) -> bool {
self.titles
.iter()
.any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
@@ -765,11 +774,11 @@ impl Block<'_> {
}
}
fn render_titles(&self, area: Rect, buf: &mut Buffer) {
self.render_title_position(Position::Top, area, buf);
self.render_title_position(Position::Bottom, area, buf);
self.render_title_position(TitlePosition::Top, area, buf);
self.render_title_position(TitlePosition::Bottom, area, buf);
}
fn render_title_position(&self, position: Position, area: Rect, buf: &mut Buffer) {
fn render_title_position(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
// NOTE: the order in which these functions are called defines the overlapping behavior
self.render_right_titles(position, area, buf);
self.render_center_titles(position, area, buf);
@@ -783,7 +792,7 @@ impl Block<'_> {
/// the left side of that leftmost that is cut off. This is due to the line being truncated
/// incorrectly. See <https://github.com/ratatui/ratatui/issues/932>
#[expect(clippy::similar_names)]
fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
fn render_right_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let titles = self.filtered_titles(position, Alignment::Right);
let mut titles_area = self.titles_area(area, position);
@@ -818,7 +827,7 @@ impl Block<'_> {
/// ideal and should be fixed in the future to align the titles to the center of the block and
/// truncate both sides of the titles if the block is too small to fit all titles.
#[expect(clippy::similar_names)]
fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let titles = self
.filtered_titles(position, Alignment::Center)
.collect_vec();
@@ -853,7 +862,7 @@ impl Block<'_> {
/// Render titles aligned to the left of the block
#[expect(clippy::similar_names)]
fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
fn render_left_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let titles = self.filtered_titles(position, Alignment::Left);
let mut titles_area = self.titles_area(area, position);
for title in titles {
@@ -877,7 +886,7 @@ impl Block<'_> {
/// An iterator over the titles that match the position and alignment
fn filtered_titles(
&self,
position: Position,
position: TitlePosition,
alignment: Alignment,
) -> impl DoubleEndedIterator<Item = &Line<'_>> {
self.titles
@@ -889,14 +898,14 @@ impl Block<'_> {
/// An area that is one line tall and spans the width of the block excluding the borders and
/// is positioned at the top or bottom of the block.
fn titles_area(&self, area: Rect, position: Position) -> Rect {
fn titles_area(&self, area: Rect, position: TitlePosition) -> Rect {
let left_border = u16::from(self.borders.contains(Borders::LEFT));
let right_border = u16::from(self.borders.contains(Borders::RIGHT));
Rect {
x: area.left() + left_border,
y: match position {
Position::Top => area.top(),
Position::Bottom => area.bottom() - 1,
TitlePosition::Top => area.top(),
TitlePosition::Bottom => area.bottom() - 1,
},
width: area
.width
@@ -927,10 +936,10 @@ impl Block<'_> {
/// account when calculating the result.
pub(crate) fn vertical_space(&self) -> (u16, u16) {
let has_top =
self.borders.contains(Borders::TOP) || self.has_title_at_position(Position::Top);
self.borders.contains(Borders::TOP) || self.has_title_at_position(TitlePosition::Top);
let top = self.padding.top + u16::from(has_top);
let has_bottom =
self.borders.contains(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom);
let has_bottom = self.borders.contains(Borders::BOTTOM)
|| self.has_title_at_position(TitlePosition::Bottom);
let bottom = self.padding.bottom + u16::from(has_bottom);
(top, bottom)
}
@@ -970,7 +979,7 @@ mod tests {
use alloc::{format, vec};
use itertools::iproduct;
use ratatui_core::layout::{HorizontalAlignment, Offset};
use ratatui_core::layout::Offset;
use ratatui_core::style::{Color, Modifier, Stylize};
use rstest::rstest;
use strum::ParseError;
@@ -1040,50 +1049,20 @@ mod tests {
#[test]
fn has_title_at_position_takes_into_account_all_positioning_declarations() {
let block = Block::new();
assert!(!block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
assert!(!block.has_title_at_position(TitlePosition::Top));
assert!(!block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_top("test");
assert!(block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
assert!(block.has_title_at_position(TitlePosition::Top));
assert!(!block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_bottom("test");
assert!(!block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[expect(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Top))
.title_position(Position::Bottom);
assert!(block.has_title_at_position(Position::Top));
assert!(!block.has_title_at_position(Position::Bottom));
#[expect(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Bottom))
.title_position(Position::Top);
assert!(!block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
assert!(!block.has_title_at_position(TitlePosition::Top));
assert!(block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_top("test").title_bottom("test");
assert!(block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[expect(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test").position(Position::Top))
.title(Title::from("Test"))
.title_position(Position::Bottom);
assert!(block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
#[expect(deprecated)] // until Title is removed
let block = Block::new()
.title(Title::from("Test"))
.title(Title::from("Test").position(Position::Bottom))
.title_position(Position::Top);
assert!(block.has_title_at_position(Position::Top));
assert!(block.has_title_at_position(Position::Bottom));
assert!(block.has_title_at_position(TitlePosition::Top));
assert!(block.has_title_at_position(TitlePosition::Bottom));
}
#[rstest]
@@ -1133,18 +1112,18 @@ mod tests {
}
#[rstest]
#[case::top_border_top_title(Block::new(), Borders::TOP, Position::Top, (1, 0))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Top, (1, 0))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Top, (1, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Top, (1, 0))]
#[case::top_border_top_title(Block::new(), Borders::TOP, Position::Bottom, (1, 1))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Bottom, (0, 1))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Bottom, (0, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Bottom, (0, 1))]
#[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Top, (1, 0))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Top, (1, 0))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Top, (1, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Top, (1, 0))]
#[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Bottom, (1, 1))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Bottom, (0, 1))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Bottom, (0, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Bottom, (0, 1))]
fn vertical_space_takes_into_account_borders_and_title(
#[case] block: Block,
#[case] borders: Borders,
#[case] pos: Position,
#[case] pos: TitlePosition,
#[case] vertical_space: (u16, u16),
) {
let block = block.borders(borders).title_position(pos).title("Test");
@@ -1210,7 +1189,7 @@ mod tests {
titles: Vec::new(),
titles_style: Style::new(),
titles_alignment: Alignment::Left,
titles_position: Position::Top,
titles_position: TitlePosition::Top,
borders: Borders::NONE,
border_style: Style::new(),
border_set: BorderType::Plain.to_border_set(),
@@ -1231,7 +1210,7 @@ mod tests {
// .border_style(_DEFAULT_STYLE) // no longer const
// .title_style(_DEFAULT_STYLE) // no longer const
.title_alignment(Alignment::Left)
.title_position(Position::Top)
.title_position(TitlePosition::Top)
.padding(_DEFAULT_PADDING);
}
@@ -1292,29 +1271,6 @@ mod tests {
);
}
#[test]
fn title() {
use HorizontalAlignment::*;
use Position::*;
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
#[expect(deprecated)] // until Title is removed
Block::bordered()
.title(Title::from("A").position(Top).alignment(Left))
.title(Title::from("B").position(Top).alignment(Center))
.title(Title::from("C").position(Top).alignment(Right))
.title(Title::from("D").position(Bottom).alignment(Left))
.title(Title::from("E").position(Bottom).alignment(Center))
.title(Title::from("F").position(Bottom).alignment(Right))
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌A───B───C┐",
"│ │",
"└D───E───F┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn title_top_bottom() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
@@ -1384,7 +1340,7 @@ mod tests {
fn title_position() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
Block::new()
.title_position(Position::Bottom)
.title_position(TitlePosition::Bottom)
.title("test")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" ", "test"]));

View File

@@ -1,185 +0,0 @@
//! This module holds the [`Title`] element and its related configuration types.
//! A title is a piece of [`Block`](crate::block::Block) configuration.
use ratatui_core::layout::Alignment;
use ratatui_core::text::Line;
use strum::{Display, EnumString};
/// A [`Block`](crate::block::Block) title.
///
/// It can be aligned (see [`Alignment`]) and positioned (see [`Position`]).
///
/// # Future Deprecation
///
/// This type is deprecated and will be removed in a future release. The reason for this is that the
/// position of the title should be stored in the block itself, not in the title. The `Line` type
/// has an alignment method that can be used to align the title. For more information see
/// <https://github.com/ratatui/ratatui/issues/738>.
///
/// Use [`Line`] instead, when the position is not defined as part of the title. When a specific
/// position is needed, use [`Block::title_top`](crate::block::Block::title_top) or
/// [`Block::title_bottom`](crate::block::Block::title_bottom) instead.
///
/// # Example
///
/// Title with no style.
/// ```
/// use ratatui::widgets::block::Title;
///
/// Title::from("Title");
/// ```
///
/// Blue title on a white background (via [`Stylize`](ratatui_core::style::Stylize) trait).
/// ```
/// use ratatui::style::Stylize;
/// use ratatui::widgets::block::Title;
///
/// Title::from("Title".blue().on_white());
/// ```
///
/// Title with multiple styles (see [`Line`] and [`Stylize`](ratatui_core::style::Stylize)).
/// ```
/// use ratatui::style::Stylize;
/// use ratatui::text::Line;
/// use ratatui::widgets::block::Title;
///
/// Title::from(Line::from(vec!["Q".white().underlined(), "uit".gray()]));
/// ```
///
/// Complete example
/// ```
/// use ratatui::layout::Alignment;
/// use ratatui::widgets::Block;
/// use ratatui::widgets::block::{Position, Title};
///
/// Title::from("Title")
/// .position(Position::Top)
/// .alignment(Alignment::Right);
/// ```
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[deprecated = "use `title_top()` or `title_bottom()` instead"]
pub struct Title<'a> {
/// Title content
pub content: Line<'a>,
/// Title alignment
///
/// If [`None`], defaults to the alignment defined with
/// [`Block::title_alignment`](crate::block::Block::title_alignment) in the associated
/// [`Block`](crate::block::Block).
pub alignment: Option<Alignment>,
/// Title position
///
/// If [`None`], defaults to the position defined with
/// [`Block::title_position`](crate::block::Block::title_position) in the associated
/// [`Block`](crate::block::Block).
pub position: Option<Position>,
}
/// Defines the [title](crate::block::Title) position.
///
/// The title can be positioned on top or at the bottom of the block.
/// Defaults to [`Position::Top`].
///
/// # Example
///
/// ```
/// use ratatui::widgets::Block;
/// use ratatui::widgets::block::{Position, Title};
///
/// Block::new().title(Title::from("title").position(Position::Bottom));
/// ```
#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Position {
/// Position the title at the top of the block.
///
/// This is the default.
#[default]
Top,
/// Position the title at the bottom of the block.
Bottom,
}
impl<'a> Title<'a> {
/// Set the title content.
#[must_use = "method moves the value of self and returns the modified value"]
pub fn content<T>(mut self, content: T) -> Self
where
T: Into<Line<'a>>,
{
self.content = content.into();
self
}
/// Set the title alignment.
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = Some(alignment);
self
}
/// Set the title position.
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn position(mut self, position: Position) -> Self {
self.position = Some(position);
self
}
}
impl<'a, T> From<T> for Title<'a>
where
T: Into<Line<'a>>,
{
fn from(value: T) -> Self {
let content = value.into();
let alignment = content.alignment;
Self {
content,
alignment,
position: None,
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use rstest::rstest;
use strum::ParseError;
use super::*;
#[test]
fn position_to_string() {
assert_eq!(Position::Top.to_string(), "Top");
assert_eq!(Position::Bottom.to_string(), "Bottom");
}
#[test]
fn position_from_str() {
assert_eq!("Top".parse::<Position>(), Ok(Position::Top));
assert_eq!("Bottom".parse::<Position>(), Ok(Position::Bottom));
assert_eq!("".parse::<Position>(), Err(ParseError::VariantNotFound));
}
#[test]
fn title_from_line() {
let title = Title::from(Line::raw("Title"));
assert_eq!(title.content, Line::from("Title"));
assert_eq!(title.alignment, None);
assert_eq!(title.position, None);
}
#[rstest]
#[case::left(Alignment::Left)]
#[case::center(Alignment::Center)]
#[case::right(Alignment::Right)]
fn title_from_line_with_alignment(#[case] alignment: Alignment) {
let line = Line::raw("Title").alignment(alignment);
let title = Title::from(line.clone());
assert_eq!(title.content, line);
assert_eq!(title.alignment, Some(alignment));
assert_eq!(title.position, None);
}
}

View File

@@ -502,7 +502,7 @@ mod tests {
use ratatui_core::widgets::Widget;
use super::*;
use crate::block::Position;
use crate::block::TitlePosition;
use crate::borders::Borders;
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
@@ -700,7 +700,7 @@ mod tests {
fn test_render_paragraph_with_block_with_bottom_title_and_border() {
let block = Block::new()
.borders(Borders::BOTTOM)
.title_position(Position::Bottom)
.title_position(TitlePosition::Bottom)
.title("Title");
let paragraph = Paragraph::new("Hello, world!").block(block);
test_case(

View File

@@ -43,6 +43,5 @@ pub use crate::layout::{
};
pub use crate::style::{self, Color, Modifier, Style, Stylize};
pub use crate::text::{self, Line, Masked, Span, Text};
pub use crate::widgets::block::BlockExt;
pub use crate::widgets::{StatefulWidget, Widget};
pub use crate::widgets::{BlockExt, StatefulWidget, Widget};
pub use crate::{Frame, Terminal, symbols};

View File

@@ -32,9 +32,7 @@
pub use ratatui_core::widgets::{StatefulWidget, Widget};
pub use ratatui_widgets::barchart::{Bar, BarChart, BarGroup};
// TODO remove this module once title etc. are gone
pub use ratatui_widgets::block;
pub use ratatui_widgets::block::{Block, Padding};
pub use ratatui_widgets::block::{Block, BlockExt, Padding, TitlePosition};
pub use ratatui_widgets::borders::{BorderType, Borders};
#[cfg(feature = "widget-calendar")]
pub use ratatui_widgets::calendar;