Implement style merging for list/table styles

The highlight (and header) styles are stored as `StyleDiff`, which get
applied on top of the underlying base style. The base style depends on
the item type.

- For unstyled data (`Text::Raw`, `Row::Data`) the list/table `style` is
  used as the default.
- For styled data the (`Text::Styled`, `Row::StyledData`) the individual
  row style overrides the list/table `style`.

This is patched with `hilight_style_diff` and `header_style_diff` to
acquire the final style.
pull/317/head
Mikko Rantanen 4 years ago
parent 10f4334a26
commit 81e9621a94
No known key found for this signature in database
GPG Key ID: B680E9CE10A55798

@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
use crate::buffer::Buffer;
use crate::layout::{Corner, Rect};
use crate::style::Style;
use crate::style::{Style, StyleDiff};
use crate::widgets::{Block, StatefulWidget, Text, Widget};
#[derive(Debug, Clone)]
@ -60,7 +60,7 @@ where
/// Base style of the widget
style: Style,
/// Style used to render selected item
highlight_style: Style,
highlight_style_diff: StyleDiff,
/// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol: Option<&'b str>,
}
@ -70,14 +70,7 @@ where
L: Iterator<Item = Text<'b>> + Default,
{
fn default() -> List<'b, L> {
List {
block: None,
items: L::default(),
style: Default::default(),
start_corner: Corner::TopLeft,
highlight_style: Style::default(),
highlight_symbol: None,
}
Self::new(L::default())
}
}
@ -91,7 +84,7 @@ where
items,
style: Default::default(),
start_corner: Corner::TopLeft,
highlight_style: Style::default(),
highlight_style_diff: Style::default().into(),
highlight_symbol: None,
}
}
@ -120,7 +113,12 @@ where
}
pub fn highlight_style(mut self, highlight_style: Style) -> List<'b, L> {
self.highlight_style = highlight_style;
self.highlight_style_diff = highlight_style.into();
self
}
pub fn highlight_style_diff(mut self, highlight_style: StyleDiff) -> List<'b, L> {
self.highlight_style_diff = highlight_style;
self
}
@ -153,10 +151,10 @@ where
buf.set_background(list_area, self.style.bg);
// Use highlight_style only if something is selected
let (selected, highlight_style) = match state.selected {
Some(i) => (Some(i), self.highlight_style),
None => (None, self.style),
// Use highlight_style_diff only if something is selected
let (selected, highlight_style_diff) = match state.selected {
Some(i) => (Some(i), self.highlight_style_diff),
None => (None, StyleDiff::default()),
};
let highlight_symbol = self.highlight_symbol.unwrap_or("");
let blank_symbol = iter::repeat(" ")
@ -188,32 +186,44 @@ where
// Not supported
_ => (list_area.left(), list_area.top() + i as u16),
};
let (elem_x, style) = if let Some(s) = selected {
let (elem_x, highlight_style_diff) = if let Some(s) = selected {
if s == i + state.offset {
let (x, _) = buf.set_stringn(
x,
y,
highlight_symbol,
list_area.width as usize,
highlight_style,
self.style.patch(highlight_style_diff),
);
(x, Some(highlight_style))
(x, highlight_style_diff)
} else {
let (x, _) =
buf.set_stringn(x, y, &blank_symbol, list_area.width as usize, self.style);
(x, None)
(x, StyleDiff::default())
}
} else {
(x, None)
(x, StyleDiff::default())
};
let max_element_width = (list_area.width - (elem_x - x)) as usize;
match item {
Text::Raw(ref v) => {
buf.set_stringn(elem_x, y, v, max_element_width, style.unwrap_or(self.style));
buf.set_stringn(
elem_x,
y,
v,
max_element_width,
self.style.patch(highlight_style_diff),
);
}
Text::Styled(ref v, s) => {
buf.set_stringn(elem_x, y, v, max_element_width, style.unwrap_or(s));
buf.set_stringn(
elem_x,
y,
v,
max_element_width,
s.patch(highlight_style_diff),
);
}
};
}

@ -1,7 +1,7 @@
use crate::{
buffer::Buffer,
layout::{Constraint, Rect},
style::Style,
style::{Style, StyleDiff},
widgets::{Block, StatefulWidget, Widget},
};
use cassowary::{
@ -88,7 +88,7 @@ pub struct Table<'a, H, R> {
/// Header row for all columns
header: H,
/// Style for the header
header_style: Style,
header_style_diff: StyleDiff,
/// Width constraints for each column
widths: &'a [Constraint],
/// Space between each column
@ -96,7 +96,7 @@ pub struct Table<'a, H, R> {
/// Space between the header and the rows
header_gap: u16,
/// Style used to render the selected row
highlight_style: Style,
highlight_style_diff: StyleDiff,
/// Symbol in front of the selected rom
highlight_symbol: Option<&'a str>,
/// Data to display in each row
@ -113,11 +113,11 @@ where
block: None,
style: Style::default(),
header: H::default(),
header_style: Style::default(),
header_style_diff: Style::default().into(),
widths: &[],
column_spacing: 1,
header_gap: 1,
highlight_style: Style::default(),
highlight_style_diff: Style::default().into(),
highlight_symbol: None,
rows: R::default(),
}
@ -135,11 +135,11 @@ where
block: None,
style: Style::default(),
header,
header_style: Style::default(),
header_style_diff: Style::default().into(),
widths: &[],
column_spacing: 1,
header_gap: 1,
highlight_style: Style::default(),
highlight_style_diff: Style::default().into(),
highlight_symbol: None,
rows,
}
@ -158,7 +158,12 @@ where
}
pub fn header_style(mut self, style: Style) -> Table<'a, H, R> {
self.header_style = style;
self.header_style_diff = style.into();
self
}
pub fn header_style_diff(mut self, style: StyleDiff) -> Table<'a, H, R> {
self.header_style_diff = style;
self
}
@ -194,7 +199,12 @@ where
}
pub fn highlight_style(mut self, highlight_style: Style) -> Table<'a, H, R> {
self.highlight_style = highlight_style;
self.highlight_style_diff = highlight_style.into();
self
}
pub fn highlight_style_diff(mut self, highlight_style: StyleDiff) -> Table<'a, H, R> {
self.highlight_style_diff = highlight_style;
self
}
@ -283,16 +293,22 @@ where
// Draw header
if y < table_area.bottom() {
for (w, t) in solved_widths.iter().zip(self.header.by_ref()) {
buf.set_stringn(x, y, format!("{}", t), *w as usize, self.header_style);
buf.set_stringn(
x,
y,
format!("{}", t),
*w as usize,
self.style.patch(self.header_style_diff),
);
x += *w + self.column_spacing;
}
}
y += 1 + self.header_gap;
// Use highlight_style only if something is selected
let (selected, highlight_style) = match state.selected {
Some(i) => (Some(i), self.highlight_style),
None => (None, self.style),
let (selected, highlight_style_diff) = match state.selected {
Some(i) => (Some(i), self.highlight_style_diff),
None => (None, StyleDiff::default()),
};
let highlight_symbol = self.highlight_symbol.unwrap_or("");
let blank_symbol = iter::repeat(" ")
@ -300,7 +316,6 @@ where
.collect::<String>();
// Draw rows
let default_style = Style::default();
if y < table_area.bottom() {
let remaining = (table_area.bottom() - y) as usize;
@ -317,23 +332,40 @@ where
0
};
for (i, row) in self.rows.skip(state.offset).take(remaining).enumerate() {
let (data, style, symbol) = match row {
Row::Data(d) | Row::StyledData(d, _)
let (data, style, symbol, symbol_style) = match row {
Row::Data(d) if Some(i) == state.selected.map(|s| s - state.offset) => (
d,
self.style.patch(highlight_style_diff),
highlight_symbol,
self.style.patch(highlight_style_diff),
),
Row::StyledData(d, s)
if Some(i) == state.selected.map(|s| s - state.offset) =>
{
(d, highlight_style, highlight_symbol)
(
d,
s.patch(highlight_style_diff),
highlight_symbol,
self.style.patch(highlight_style_diff),
)
}
Row::Data(d) => (d, default_style, blank_symbol.as_ref()),
Row::StyledData(d, s) => (d, s, blank_symbol.as_ref()),
Row::Data(d) => (d, self.style, blank_symbol.as_ref(), self.style),
Row::StyledData(d, s) => (d, s, blank_symbol.as_ref(), self.style),
};
x = table_area.left();
for (c, (w, elt)) in solved_widths.iter().zip(data).enumerate() {
let s = if c == 0 {
format!("{}{}", symbol, elt)
if c == 0 {
buf.set_stringn(x, y + i as u16, symbol, *w as usize, symbol_style);
buf.set_stringn(
x + symbol.len() as u16,
y + i as u16,
elt.to_string(),
*w as usize,
style,
);
} else {
format!("{}", elt)
buf.set_stringn(x, y + i as u16, elt.to_string(), *w as usize, style);
};
buf.set_stringn(x, y + i as u16, s, *w as usize, style);
x += *w + self.column_spacing;
}
}

@ -2,7 +2,7 @@ use tui::{
backend::TestBackend,
buffer::Buffer,
layout::Rect,
style::{Color, Style},
style::{Color, Style, StyleDiff},
symbols,
widgets::{Block, Borders, List, ListState, Text},
Terminal,
@ -35,6 +35,51 @@ fn widgets_list_should_highlight_the_selected_item() {
terminal.backend().assert_buffer(&expected);
}
#[test]
fn widgets_list_should_merge_styles_in_correct_order() {
let backend = TestBackend::new(10, 3);
let mut terminal = Terminal::new(backend).unwrap();
let mut state = ListState::default();
state.select(Some(1));
terminal
.draw(|mut f| {
let size = f.size();
let items = vec![
Text::raw("Item 1"),
Text::styled("Item 2", Style::default().bg(Color::Green)),
Text::styled("Item 3", Style::default().bg(Color::Green)),
];
let list = List::new(items.into_iter())
.style(Style::default().bg(Color::Yellow))
.highlight_style_diff(StyleDiff::default().bg(Color::Red))
.highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]);
// `style` acts as the base style.
for x in 0..10 {
for y in 0..3 {
expected.get_mut(x, y).set_bg(Color::Yellow);
}
}
// item style covers only the individual items.
for x in 3..9 {
for y in 1..3 {
expected.get_mut(x, y).set_bg(Color::Green);
}
}
// `higlight_style` overrides both.
for x in 0..9 {
expected.get_mut(x, 1).set_bg(Color::Red);
}
terminal.backend().assert_buffer(&expected);
}
#[test]
fn widgets_list_should_truncate_items() {
let backend = TestBackend::new(10, 2);
@ -99,7 +144,7 @@ fn widgets_list_can_be_styled() {
];
let list = List::new(items.into_iter())
.style(style)
.highlight_style(highlight_style)
.highlight_style_diff(highlight_style)
.highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state);
})
@ -155,7 +200,7 @@ fn widgets_list_can_be_styled() {
]),
Style::default(),
Style::default(),
Style::default(),
Style::default().into(),
);
test_case(
@ -171,7 +216,7 @@ fn widgets_list_can_be_styled() {
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
Style::default(),
Style::default().into(),
);
test_case(
@ -187,6 +232,22 @@ fn widgets_list_can_be_styled() {
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
Style::default().fg(Color::Green).bg(Color::Green),
Style::default().fg(Color::Green).bg(Color::Green).into(),
);
test_case(
Buffer::with_lines(vec![
"RRRRRRRRRR", //
"GGGGGGGGRR", //
"RRRBBBBBRR", //
]),
Buffer::with_lines(vec![
"RRRRRRRR ", //
"RRRBBBBB ", //
"RRRBBBBB ", //
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
StyleDiff::default().bg(Color::Green),
);
}

@ -1,7 +1,7 @@
use tui::backend::TestBackend;
use tui::buffer::Buffer;
use tui::layout::Constraint;
use tui::style::{Color, Style};
use tui::style::{Color, Style, StyleDiff};
use tui::widgets::{Block, Borders, Row, Table, TableState};
use tui::Terminal;
@ -440,8 +440,8 @@ fn widgets_table_can_be_styled() {
.block(Block::default().borders(Borders::ALL))
.highlight_symbol(">> ")
.style(style)
.highlight_style(highlight_style)
.header_style(header_style)
.highlight_style_diff(highlight_style)
.header_style_diff(header_style)
.widths(&[
Constraint::Length(9),
Constraint::Length(6),
@ -531,8 +531,8 @@ fn widgets_table_can_be_styled() {
]),
Style::default(),
Style::default(),
Style::default(),
Style::default(),
Style::default().into(),
Style::default().into(),
);
test_case(
@ -540,10 +540,10 @@ fn widgets_table_can_be_styled() {
"┌────────────────────────────┐",
"│YYYYYrrrrrYYYYYrrYYYYYrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│--------rr-----rr-----rrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│GGGGGGGGrrGGGGGrrGGGGGrrrrrr│",
"│BBBBBBBBrrBBBBBrrBBBBBrrrrrr│",
"│--------rr-----rr-----rrrrrr│",
"│rrrBBBBBrrBBBBBrrBBBBBrrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"└────────────────────────────┘",
@ -552,18 +552,18 @@ fn widgets_table_can_be_styled() {
"┌────────────────────────────┐",
"│YYYYY YYYYY YYYYY │",
"│ │",
"│-------- ----- ----- │",
"│rrrRRRRR RRRRR RRRRR │",
"│GGGGGGGG GGGGG GGGGG │",
"│BBBBBBBB BBBBB BBBBB │",
"│-------- ----- ----- │",
"│rrrBBBBB BBBBB BBBBB │",
"│rrrRRRRR RRRRR RRRRR │",
"│ │",
"│ │",
"└────────────────────────────┘",
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
Style::default().fg(Color::Green).bg(Color::Green),
Style::default().fg(Color::Yellow).bg(Color::Yellow),
Style::default().fg(Color::Green).bg(Color::Green).into(),
Style::default().fg(Color::Yellow).bg(Color::Yellow).into(),
);
test_case(
@ -571,10 +571,10 @@ fn widgets_table_can_be_styled() {
"┌────────────────────────────┐",
"│YYYYYrrrrrYYYYYrrYYYYYrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│--------rr-----rr-----rrrrrr│",
"│--------rr-----rr-----rrrrrr│",
"│BBBBBBBBrrBBBBBrrBBBBBrrrrrr│",
"│--------rr-----rr-----rrrrrr│",
"│rrrBBBBBrrBBBBBrrBBBBBrrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"└────────────────────────────┘",
@ -583,17 +583,48 @@ fn widgets_table_can_be_styled() {
"┌────────────────────────────┐",
"│YYYYY YYYYY YYYYY │",
"│ │",
"│rrrRRRRR RRRRR RRRRR │",
"│-------- ----- ----- │",
"│-------- ----- ----- │",
"│BBBBBBBB BBBBB BBBBB │",
"│-------- ----- ----- │",
"│rrrBBBBB BBBBB BBBBB │",
"│rrrRRRRR RRRRR RRRRR │",
"│ │",
"│ │",
"└────────────────────────────┘",
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
Style::default(),
Style::default().fg(Color::Yellow).bg(Color::Yellow),
Style::default().into(),
Style::default().fg(Color::Yellow).bg(Color::Yellow).into(),
);
test_case(
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│RRRRRrrrrrRRRRRrrRRRRRrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│GGGGGGGGrrGGGGGrrGGGGGrrrrrr│",
"│rrrBBBBBrrBBBBBrrBBBBBrrrrrr│",
"│rrrRRRRRrrRRRRRrrRRRRRrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"│rrrrrrrrrrrrrrrrrrrrrrrrrrrr│",
"└────────────────────────────┘",
]),
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│YYYYY YYYYY YYYYY │",
"│ │",
"│rrrRRRRR RRRRR RRRRR │",
"│RRRBBBBB BBBBB BBBBB │",
"│rrrBBBBB BBBBB BBBBB │",
"│rrrRRRRR RRRRR RRRRR │",
"│ │",
"│ │",
"└────────────────────────────┘",
]),
Style::default().fg(Color::Red).bg(Color::Red),
Style::default().fg(Color::Blue).bg(Color::Blue),
StyleDiff::default().bg(Color::Green),
StyleDiff::default().fg(Color::Yellow),
);
}

Loading…
Cancel
Save