mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-28 05:21:23 +00:00
feat(list)!: highlight symbol styling (#1595)
Allow styling for `List`'s highlight symbol This change makes it so anything that implements `Into<Line>` can be used as a highlight symbol. BREAKING CHANGE: `List::highlight_symbol` can no longer be used in const context BREAKING CHANGE: `List::highlight_symbol` accepted `&str`. Conversion methods that rely on type inference will need to be rewritten as the compiler cannot infer the type. closes: https://github.com/ratatui/ratatui/issues/1443 --------- Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
This commit is contained in:
parent
5710b7a8d9
commit
92a19cb604
@ -13,6 +13,7 @@ This is a quick summary of the sections below:
|
||||
- [Unreleased](#unreleased)
|
||||
- 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`
|
||||
- [v0.29.0](#v0290)
|
||||
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
|
||||
- Removed public fields from `Rect` iterators
|
||||
@ -77,6 +78,13 @@ This is a quick summary of the sections below:
|
||||
|
||||
## Unreleased (0.30.0)
|
||||
|
||||
### `List::highlight_symbol` accepts `Into<Line>` ([#1595])
|
||||
|
||||
[#1595]: https://github.com/ratatui/ratatui/pull/1595
|
||||
|
||||
Previously `List::highlight_symbol` accepted `&str`. Any code that uses conversion methods will need
|
||||
to be rewritten. Since `Into::into` is not const, this function cannot be called in const context.
|
||||
|
||||
### `FrameExt` trait for `unstable-widget-ref` feature ([#1530])
|
||||
|
||||
[#1530]: https://github.com/ratatui/ratatui/pull/1530
|
||||
|
@ -91,7 +91,7 @@ pub fn render_bottom_list(frame: &mut Frame, area: Rect) {
|
||||
let list = List::new(items)
|
||||
.style(Color::White)
|
||||
.highlight_style(Style::new().yellow().italic())
|
||||
.highlight_symbol("> ")
|
||||
.highlight_symbol("> ".red())
|
||||
.scroll_padding(1)
|
||||
.direction(ListDirection::BottomToTop)
|
||||
.repeat_highlight_symbol(true);
|
||||
|
@ -1,6 +1,9 @@
|
||||
//! The [`List`] widget is used to display a list of items and allows selecting one or multiple
|
||||
//! items.
|
||||
use ratatui_core::style::{Style, Styled};
|
||||
use ratatui_core::{
|
||||
style::{Style, Styled},
|
||||
text::Line,
|
||||
};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
pub use self::{item::ListItem, state::ListState};
|
||||
@ -116,7 +119,7 @@ pub struct List<'a> {
|
||||
/// Style used to render selected item
|
||||
pub(crate) highlight_style: Style,
|
||||
/// Symbol in front of the selected item (Shift all items to the right)
|
||||
pub(crate) highlight_symbol: Option<&'a str>,
|
||||
pub(crate) highlight_symbol: Option<Line<'a>>,
|
||||
/// Whether to repeat the highlight symbol for each line of the selected item
|
||||
pub(crate) repeat_highlight_symbol: bool,
|
||||
/// Decides when to allocate spacing for the selection symbol
|
||||
@ -298,8 +301,8 @@ impl<'a> List<'a> {
|
||||
/// let list = List::new(items).highlight_symbol(">>");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn highlight_symbol(mut self, highlight_symbol: &'a str) -> Self {
|
||||
self.highlight_symbol = Some(highlight_symbol);
|
||||
pub fn highlight_symbol<L: Into<Line<'a>>>(mut self, highlight_symbol: L) -> Self {
|
||||
self.highlight_symbol = Some(highlight_symbol.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
use ratatui_core::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
text::{Line, ToLine},
|
||||
widgets::{StatefulWidget, Widget},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
block::BlockExt,
|
||||
@ -62,8 +62,14 @@ impl StatefulWidget for &List<'_> {
|
||||
state.offset = first_visible_index;
|
||||
|
||||
// Get our set highlighted symbol (if one was set)
|
||||
let highlight_symbol = self.highlight_symbol.unwrap_or("");
|
||||
let blank_symbol = " ".repeat(highlight_symbol.width());
|
||||
let default_highlight_symbol = Line::default();
|
||||
let highlight_symbol = self
|
||||
.highlight_symbol
|
||||
.as_ref()
|
||||
.unwrap_or(&default_highlight_symbol);
|
||||
let highlight_symbol_width = highlight_symbol.width() as u16;
|
||||
let empty_symbol = " ".repeat(highlight_symbol_width as usize);
|
||||
let empty_symbol = empty_symbol.to_line();
|
||||
|
||||
let mut current_height = 0;
|
||||
let selection_spacing = self.highlight_spacing.should_add(state.selected.is_some());
|
||||
@ -83,12 +89,7 @@ impl StatefulWidget for &List<'_> {
|
||||
pos
|
||||
};
|
||||
|
||||
let row_area = Rect {
|
||||
x,
|
||||
y,
|
||||
width: list_area.width,
|
||||
height: item.height() as u16,
|
||||
};
|
||||
let row_area = Rect::new(x, y, list_area.width, item.height() as u16);
|
||||
|
||||
let item_style = self.style.patch(item.style);
|
||||
buf.set_style(row_area, item_style);
|
||||
@ -96,7 +97,6 @@ impl StatefulWidget for &List<'_> {
|
||||
let is_selected = state.selected == Some(i);
|
||||
|
||||
let item_area = if selection_spacing {
|
||||
let highlight_symbol_width = self.highlight_symbol.unwrap_or("").width() as u16;
|
||||
Rect {
|
||||
x: row_area.x + highlight_symbol_width,
|
||||
width: row_area.width.saturating_sub(highlight_symbol_width),
|
||||
@ -107,29 +107,23 @@ impl StatefulWidget for &List<'_> {
|
||||
};
|
||||
Widget::render(&item.content, item_area, buf);
|
||||
|
||||
if is_selected {
|
||||
buf.set_style(row_area, self.highlight_style);
|
||||
}
|
||||
if selection_spacing {
|
||||
for j in 0..item.content.height() {
|
||||
// if the item is selected, we need to display the highlight symbol:
|
||||
// - either for the first line of the item only,
|
||||
// - or for each line of the item if the appropriate option is set
|
||||
let symbol = if is_selected && (j == 0 || self.repeat_highlight_symbol) {
|
||||
let line = if is_selected && (j == 0 || self.repeat_highlight_symbol) {
|
||||
highlight_symbol
|
||||
} else {
|
||||
&blank_symbol
|
||||
&empty_symbol
|
||||
};
|
||||
buf.set_stringn(
|
||||
x,
|
||||
y + j as u16,
|
||||
symbol,
|
||||
list_area.width as usize,
|
||||
item_style,
|
||||
);
|
||||
let highlight_area = Rect::new(x, y + j as u16, highlight_symbol_width, 1);
|
||||
line.render(highlight_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
if is_selected {
|
||||
buf.set_style(row_area, self.highlight_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -675,6 +669,25 @@ mod tests {
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlight_symbol_style_and_style() {
|
||||
let list = List::new(["Item 0", "Item 1", "Item 2"])
|
||||
.highlight_symbol(Line::from(">>").red().bold())
|
||||
.highlight_style(Style::default().fg(Color::Yellow));
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(1));
|
||||
let buffer = stateful_widget(list, &mut state, 10, 5);
|
||||
let mut expected = Buffer::with_lines([
|
||||
" Item 0 ".into(),
|
||||
">>Item 1 ".yellow(),
|
||||
" Item 2 ".into(),
|
||||
" ".into(),
|
||||
" ".into(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 1, 2, 1), Style::new().red().bold());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlight_spacing_default_when_selected() {
|
||||
// when not selected
|
||||
@ -788,19 +801,20 @@ mod tests {
|
||||
#[test]
|
||||
fn repeat_highlight_symbol() {
|
||||
let list = List::new(["Item 0\nLine 2", "Item 1", "Item 2"])
|
||||
.highlight_symbol(">>")
|
||||
.highlight_symbol(Line::from(">>").red().bold())
|
||||
.highlight_style(Style::default().fg(Color::Yellow))
|
||||
.repeat_highlight_symbol(true);
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(0));
|
||||
let buffer = stateful_widget(list, &mut state, 10, 5);
|
||||
let expected = Buffer::with_lines([
|
||||
let mut expected = Buffer::with_lines([
|
||||
">>Item 0 ".yellow(),
|
||||
">>Line 2 ".yellow(),
|
||||
" Item 1 ".into(),
|
||||
" Item 2 ".into(),
|
||||
" ".into(),
|
||||
]);
|
||||
expected.set_style(Rect::new(0, 0, 2, 2), Style::new().red().bold());
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user