Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Block::title_top and Block::title_top_bottom #940

Merged
merged 1 commit into from Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/widgets/block.rs
Expand Up @@ -22,6 +22,20 @@
/// [`Title`] 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
/// rendered with a single space separating titles that are in the same 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.
///
/// Titles are not rendered in the corners of the block unless there is no border on that edge.
/// If the block is too small and multiple titles overlap, the border may get cut off at a corner.
///
/// ```plain
/// ┌With at least a left border───
///
/// Without left border───
/// ```
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -228,6 +242,62 @@
self
}

/// Adds a title to the top of the block.
///
/// You can provide any type that can be converted into [`Line`] including: strings, string
/// slices (`&str`), borrowed strings (`Cow<str>`), [spans](crate::text::Span), or vectors of
/// [spans](crate::text::Span) (`Vec<Span>`).
///
/// # Example
///
/// ```
/// # use ratatui::{ prelude::*, widgets::* };
/// Block::bordered()
/// .title_top("Left1") // By default in the top left corner
/// .title_top(Line::from("Left2").left_aligned())
/// .title_top(Line::from("Right").right_aligned())
/// .title_top(Line::from("Center").centered());
///
/// // Renders
/// // ┌Left1─Left2───Center─────────Right┐
/// // │ │
/// // └──────────────────────────────────┘
/// ```
#[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 {
joshka marked this conversation as resolved.
Show resolved Hide resolved
let title = Title::from(title).position(Position::Top);
self.titles.push(title);
self
}

/// Adds a title to the bottom of the block.
///
/// You can provide any type that can be converted into [`Line`] including: strings, string
/// slices (`&str`), borrowed strings (`Cow<str>`), [spans](crate::text::Span), or vectors of
/// [spans](crate::text::Span) (`Vec<Span>`).
///
/// # Example
///
/// ```
/// # use ratatui::{ prelude::*, widgets::* };
/// Block::bordered()
/// .title_bottom("Left1") // By default in the top left corner
/// .title_bottom(Line::from("Left2").left_aligned())
/// .title_bottom(Line::from("Right").right_aligned())
/// .title_bottom(Line::from("Center").centered());
///
/// // Renders
/// // ┌──────────────────────────────────┐
/// // │ │
/// // └Left1─Left2───Center─────────Right┘
/// ```
#[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 title = Title::from(title).position(Position::Bottom);
self.titles.push(title);
self
}

/// Applies the style to all titles.
///
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
Expand Down Expand Up @@ -655,6 +725,7 @@
.right()
.saturating_sub(title_width)
.max(titles_area.left()),
width: title_width.min(titles_area.width),
..titles_area
};
buf.set_style(title_area, self.titles_style);
Expand Down Expand Up @@ -1130,6 +1201,50 @@
)
}

#[test]
fn title() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
use Alignment::*;
use Position::*;
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);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┌A─────B─────C┐",
"│ │",
"└D─────E─────F┘",
])
);

Check warning on line 1224 in src/widgets/block.rs

View check run for this annotation

Codecov / codecov/patch

src/widgets/block.rs#L1218-L1224

Added lines #L1218 - L1224 were not covered by tests
}

#[test]
fn title_top_bottom() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
Block::bordered()
.title_top(Line::raw("A").left_aligned())
.title_top(Line::raw("B").centered())
.title_top(Line::raw("C").right_aligned())
.title_bottom(Line::raw("D").left_aligned())
.title_bottom(Line::raw("E").centered())
.title_bottom(Line::raw("F").right_aligned())
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┌A─────B─────C┐",
"│ │",
"└D─────E─────F┘",
])
);

Check warning on line 1245 in src/widgets/block.rs

View check run for this annotation

Codecov / codecov/patch

src/widgets/block.rs#L1239-L1245

Added lines #L1239 - L1245 were not covered by tests
}

#[test]
fn title_alignment() {
let tests = vec![
Expand Down
31 changes: 29 additions & 2 deletions src/widgets/block/title.rs
Expand Up @@ -115,18 +115,25 @@ where
T: Into<Line<'a>>,
{
fn from(value: T) -> Self {
Self::default().content(value.into())
let content = value.into();
let alignment = content.alignment;
Self {
content,
alignment,
position: None,
}
}
}

#[cfg(test)]
mod tests {
use rstest::rstest;
use strum::ParseError;

use super::*;

#[test]
fn position_tostring() {
fn position_to_string() {
assert_eq!(Position::Top.to_string(), "Top");
assert_eq!(Position::Bottom.to_string(), "Bottom");
}
Expand All @@ -137,4 +144,24 @@ mod tests {
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);
}
}