feat(list): add option to always allocate the "selection" column width (#394)

* feat(list): add option to always allocate the "selection" column width

Before this option was available, selecting a item in a list when nothing was selected
previously made the row layout change (the same applies to unselecting) by adding the width
of the "highlight symbol" in the front of the list, this option allows to configure this
behavior.

* style: change "highlight_spacing" doc comment to use inline code-block for reference
This commit is contained in:
hasezoey 2023-08-13 10:24:51 +02:00 committed by GitHub
parent 10dbd6f207
commit 4d70169bef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 269 additions and 5 deletions

View file

@ -5,7 +5,7 @@ use crate::{
layout::{Corner, Rect},
style::{Style, Styled},
text::Text,
widgets::{Block, StatefulWidget, Widget},
widgets::{Block, HighlightSpacing, StatefulWidget, Widget},
};
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
@ -103,6 +103,8 @@ pub struct List<'a> {
highlight_symbol: Option<&'a str>,
/// Whether to repeat the highlight symbol for each line of the selected item
repeat_highlight_symbol: bool,
/// Decides when to allocate spacing for the selection symbol
highlight_spacing: HighlightSpacing,
}
impl<'a> List<'a> {
@ -118,6 +120,7 @@ impl<'a> List<'a> {
highlight_style: Style::default(),
highlight_symbol: None,
repeat_highlight_symbol: false,
highlight_spacing: HighlightSpacing::default(),
}
}
@ -146,6 +149,14 @@ impl<'a> List<'a> {
self
}
/// Set when to show the highlight spacing
///
/// See [`HighlightSpacing`] about which variant affects spacing in which way
pub fn highlight_spacing(mut self, value: HighlightSpacing) -> Self {
self.highlight_spacing = value;
self
}
pub fn start_corner(mut self, corner: Corner) -> List<'a> {
self.start_corner = corner;
self
@ -228,7 +239,7 @@ impl<'a> StatefulWidget for List<'a> {
let blank_symbol = " ".repeat(highlight_symbol.width());
let mut current_height = 0;
let has_selection = state.selected.is_some();
let selection_spacing = self.highlight_spacing.should_add(state.selected.is_some());
for (i, item) in self
.items
.iter_mut()
@ -263,7 +274,7 @@ impl<'a> StatefulWidget for List<'a> {
} else {
&blank_symbol
};
let (elem_x, max_element_width) = if has_selection {
let (elem_x, max_element_width) = if selection_spacing {
let (elem_x, _) = buf.set_stringn(
x,
y + j as u16,
@ -774,6 +785,134 @@ mod tests {
assert_buffer_eq!(buffer, expected);
}
#[test]
fn test_list_highlight_spacing_default_whenselected() {
// when not selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items).highlight_symbol(">>");
let mut state = ListState::default();
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
"Item 0 ",
"Item 1 ",
"Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
// when selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items).highlight_symbol(">>");
let mut state = ListState::default();
state.select(Some(1));
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
" Item 0 ",
">>Item 1 ",
" Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
}
#[test]
fn test_list_highlight_spacing_default_always() {
// when not selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items)
.highlight_symbol(">>")
.highlight_spacing(HighlightSpacing::Always);
let mut state = ListState::default();
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
" Item 0 ",
" Item 1 ",
" Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
// when selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items)
.highlight_symbol(">>")
.highlight_spacing(HighlightSpacing::Always);
let mut state = ListState::default();
state.select(Some(1));
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
" Item 0 ",
">>Item 1 ",
" Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
}
#[test]
fn test_list_highlight_spacing_default_never() {
// when not selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items)
.highlight_symbol(">>")
.highlight_spacing(HighlightSpacing::Never);
let mut state = ListState::default();
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
"Item 0 ",
"Item 1 ",
"Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
// when selected
{
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
let list = List::new(items)
.highlight_symbol(">>")
.highlight_spacing(HighlightSpacing::Never);
let mut state = ListState::default();
state.select(Some(1));
let buffer = render_stateful_widget(list, &mut state, 10, 5);
let expected = Buffer::with_lines(vec![
"Item 0 ",
"Item 1 ",
"Item 2 ",
" ",
" ",
]);
assert_buffer_eq!(buffer, expected);
}
}
#[test]
fn test_list_repeat_highlight_symbol() {
let items = list_items(vec!["Item 0\nLine 2", "Item 1", "Item 2"]);

View file

@ -324,7 +324,7 @@ impl<'a> Table<'a> {
/// Set when to show the highlight spacing
///
/// See [HighlightSpacing] about which variant affects spacing in which way
/// See [`HighlightSpacing`] about which variant affects spacing in which way
pub fn highlight_spacing(mut self, value: HighlightSpacing) -> Self {
self.highlight_spacing = value;
self

View file

@ -7,7 +7,7 @@ use ratatui::{
style::{Color, Style},
symbols,
text::Line,
widgets::{Block, Borders, List, ListItem, ListState},
widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState},
Terminal,
};
@ -243,3 +243,128 @@ fn widget_list_should_not_ignore_empty_string_items() {
terminal.backend().assert_buffer(&expected);
}
#[test]
fn widgets_list_enable_always_highlight_spacing() {
let test_case = |state: &mut ListState, space: HighlightSpacing, expected: Buffer| {
let backend = TestBackend::new(30, 8);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let table = List::new(vec![
ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]),
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
])
.block(Block::default().borders(Borders::ALL))
.highlight_symbol(">> ")
.highlight_spacing(space);
f.render_stateful_widget(table, size, state);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
assert_eq!(HighlightSpacing::default(), HighlightSpacing::WhenSelected);
let mut state = ListState::default();
// no selection, "WhenSelected" should only allocate if selected
test_case(
&mut state,
HighlightSpacing::default(),
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
// no selection, "Always" should allocate regardless if selected or not
test_case(
&mut state,
HighlightSpacing::Always,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│ Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// no selection, "Never" should never allocate regadless if selected or not
test_case(
&mut state,
HighlightSpacing::Never,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
// select first, "WhenSelected" should only allocate if selected
state.select(Some(0));
test_case(
&mut state,
HighlightSpacing::default(),
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// select first, "Always" should allocate regardless if selected or not
state.select(Some(0));
test_case(
&mut state,
HighlightSpacing::Always,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// select first, "Never" should never allocate regadless if selected or not
state.select(Some(0));
test_case(
&mut state,
HighlightSpacing::Never,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
}