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:
@@ -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
|
||||
|
||||
@@ -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"]));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user