refactor: implement cascading styles

- merge `Style` and `StyleDiff` together. `Style` now is used to activate or deactivate certain
style rules not to overidden all of them.
- update all impacted widgets, examples and tests.
This commit is contained in:
Florian Dehau 2020-07-11 19:08:28 +02:00
parent 72ba4ff2d4
commit 0ffea495b1
31 changed files with 533 additions and 723 deletions

View file

@ -79,7 +79,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.block(Block::default().title("Data1").borders(Borders::ALL)) .block(Block::default().title("Data1").borders(Borders::ALL))
.data(&app.data) .data(&app.data)
.bar_width(9) .bar_width(9)
.style(Style::default().fg(Color::Yellow)) .bar_style(Style::default().fg(Color::Yellow))
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow)); .value_style(Style::default().fg(Color::Black).bg(Color::Yellow));
f.render_widget(barchart, chunks[0]); f.render_widget(barchart, chunks[0]);
@ -93,18 +93,26 @@ fn main() -> Result<(), Box<dyn Error>> {
.data(&app.data) .data(&app.data)
.bar_width(5) .bar_width(5)
.bar_gap(3) .bar_gap(3)
.style(Style::default().fg(Color::Green)) .bar_style(Style::default().fg(Color::Green))
.value_style(Style::default().bg(Color::Green).modifier(Modifier::BOLD)); .value_style(
Style::default()
.bg(Color::Green)
.add_modifier(Modifier::BOLD),
);
f.render_widget(barchart, chunks[0]); f.render_widget(barchart, chunks[0]);
let barchart = BarChart::default() let barchart = BarChart::default()
.block(Block::default().title("Data3").borders(Borders::ALL)) .block(Block::default().title("Data3").borders(Borders::ALL))
.data(&app.data) .data(&app.data)
.style(Style::default().fg(Color::Red)) .bar_style(Style::default().fg(Color::Red))
.bar_width(7) .bar_width(7)
.bar_gap(0) .bar_gap(0)
.value_style(Style::default().bg(Color::Red)) .value_style(Style::default().bg(Color::Red))
.label_style(Style::default().fg(Color::Cyan).modifier(Modifier::ITALIC)); .label_style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::ITALIC),
);
f.render_widget(barchart, chunks[1]); f.render_widget(barchart, chunks[1]);
})?; })?;

View file

@ -7,7 +7,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
text::Span, text::Span,
widgets::{Block, BorderType, Borders}, widgets::{Block, BorderType, Borders},
Terminal, Terminal,
@ -47,7 +47,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.split(chunks[0]); .split(chunks[0]);
let block = Block::default() let block = Block::default()
.title(vec![ .title(vec![
Span::styled("With", StyleDiff::default().fg(Color::Yellow)), Span::styled("With", Style::default().fg(Color::Yellow)),
Span::from(" background"), Span::from(" background"),
]) ])
.style(Style::default().bg(Color::Green)); .style(Style::default().bg(Color::Green));
@ -55,7 +55,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let block = Block::default().title(Span::styled( let block = Block::default().title(Span::styled(
"Styled title", "Styled title",
StyleDiff::default() Style::default()
.fg(Color::White) .fg(Color::White)
.bg(Color::Red) .bg(Color::Red)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),

View file

@ -10,7 +10,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
symbols, symbols,
text::Span, text::Span,
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType}, widgets::{Axis, Block, Borders, Chart, Dataset, GraphType},
@ -95,12 +95,12 @@ fn main() -> Result<(), Box<dyn Error>> {
let x_labels = vec![ let x_labels = vec![
Span::styled( Span::styled(
format!("{}", app.window[0]), format!("{}", app.window[0]),
StyleDiff::default().modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
), ),
Span::raw(format!("{}", (app.window[0] + app.window[1]) / 2.0)), Span::raw(format!("{}", (app.window[0] + app.window[1]) / 2.0)),
Span::styled( Span::styled(
format!("{}", app.window[1]), format!("{}", app.window[1]),
StyleDiff::default().modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
), ),
]; ];
let datasets = vec![ let datasets = vec![
@ -121,9 +121,9 @@ fn main() -> Result<(), Box<dyn Error>> {
Block::default() Block::default()
.title(Span::styled( .title(Span::styled(
"Chart 1", "Chart 1",
StyleDiff::default() Style::default()
.fg(Color::Cyan) .fg(Color::Cyan)
.modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
)) ))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
@ -139,9 +139,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.title("Y Axis") .title("Y Axis")
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.labels(vec![ .labels(vec![
Span::styled("-20", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("0"), Span::raw("0"),
Span::styled("20", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("20", Style::default().add_modifier(Modifier::BOLD)),
]) ])
.bounds([-20.0, 20.0]), .bounds([-20.0, 20.0]),
); );
@ -158,9 +158,9 @@ fn main() -> Result<(), Box<dyn Error>> {
Block::default() Block::default()
.title(Span::styled( .title(Span::styled(
"Chart 2", "Chart 2",
StyleDiff::default() Style::default()
.fg(Color::Cyan) .fg(Color::Cyan)
.modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
)) ))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
@ -170,9 +170,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.bounds([0.0, 5.0]) .bounds([0.0, 5.0])
.labels(vec![ .labels(vec![
Span::styled("0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("0", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("2.5"), Span::raw("2.5"),
Span::styled("5.0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("5.0", Style::default().add_modifier(Modifier::BOLD)),
]), ]),
) )
.y_axis( .y_axis(
@ -181,9 +181,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.bounds([0.0, 5.0]) .bounds([0.0, 5.0])
.labels(vec![ .labels(vec![
Span::styled("0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("0", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("2.5"), Span::raw("2.5"),
Span::styled("5.0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("5.0", Style::default().add_modifier(Modifier::BOLD)),
]), ]),
); );
f.render_widget(chart, chunks[1]); f.render_widget(chart, chunks[1]);
@ -199,9 +199,9 @@ fn main() -> Result<(), Box<dyn Error>> {
Block::default() Block::default()
.title(Span::styled( .title(Span::styled(
"Chart 3", "Chart 3",
StyleDiff::default() Style::default()
.fg(Color::Cyan) .fg(Color::Cyan)
.modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
)) ))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
@ -211,9 +211,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.bounds([0.0, 50.0]) .bounds([0.0, 50.0])
.labels(vec![ .labels(vec![
Span::styled("0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("0", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("25"), Span::raw("25"),
Span::styled("50", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("50", Style::default().add_modifier(Modifier::BOLD)),
]), ]),
) )
.y_axis( .y_axis(
@ -222,9 +222,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.bounds([0.0, 5.0]) .bounds([0.0, 5.0])
.labels(vec![ .labels(vec![
Span::styled("0", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("0", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("2.5"), Span::raw("2.5"),
Span::styled("5", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("5", Style::default().add_modifier(Modifier::BOLD)),
]), ]),
); );
f.render_widget(chart, chunks[2]); f.render_widget(chart, chunks[2]);

View file

@ -1,7 +1,8 @@
use crate::demo::App;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
symbols, symbols,
text::{Span, Spans}, text::{Span, Spans},
widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle},
@ -12,8 +13,6 @@ use tui::{
Frame, Frame,
}; };
use crate::demo::App;
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) { pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default() let chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
@ -22,17 +21,11 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) {
.tabs .tabs
.titles .titles
.iter() .iter()
.map(|t| { .map(|t| Spans::from(Span::styled(*t, Style::default().fg(Color::Green))))
Spans::from(vec![Span::styled(
*t,
StyleDiff::default().fg(Color::Green),
)])
})
.collect(); .collect();
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(Block::default().borders(Borders::ALL).title(app.title)) .block(Block::default().borders(Borders::ALL).title(app.title))
.style(Style::default().fg(Color::Green)) .highlight_style(Style::default().fg(Color::Yellow))
.highlight_style_diff(StyleDiff::default().fg(Color::Yellow))
.select(app.tabs.index); .select(app.tabs.index);
f.render_widget(tabs, chunks[0]); f.render_widget(tabs, chunks[0]);
match app.tabs.index { match app.tabs.index {
@ -75,11 +68,11 @@ where
let label = format!("{:.2}%", app.progress * 100.0); let label = format!("{:.2}%", app.progress * 100.0);
let gauge = Gauge::default() let gauge = Gauge::default()
.block(Block::default().title("Gauge:")) .block(Block::default().title("Gauge:"))
.style( .gauge_style(
Style::default() Style::default()
.fg(Color::Magenta) .fg(Color::Magenta)
.bg(Color::Black) .bg(Color::Black)
.modifier(Modifier::ITALIC | Modifier::BOLD), .add_modifier(Modifier::ITALIC | Modifier::BOLD),
) )
.label(label) .label(label)
.ratio(app.progress); .ratio(app.progress);
@ -129,15 +122,15 @@ where
.collect(); .collect();
let tasks = List::new(tasks) let tasks = List::new(tasks)
.block(Block::default().borders(Borders::ALL).title("List")) .block(Block::default().borders(Borders::ALL).title("List"))
.highlight_style_diff(StyleDiff::default().modifier(Modifier::BOLD)) .highlight_style(Style::default().add_modifier(Modifier::BOLD))
.highlight_symbol("> "); .highlight_symbol("> ");
f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state); f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state);
// Draw logs // Draw logs
let info_style = StyleDiff::default().fg(Color::Blue); let info_style = Style::default().fg(Color::Blue);
let warning_style = StyleDiff::default().fg(Color::Yellow); let warning_style = Style::default().fg(Color::Yellow);
let error_style = StyleDiff::default().fg(Color::Magenta); let error_style = Style::default().fg(Color::Magenta);
let critical_style = StyleDiff::default().fg(Color::Red); let critical_style = Style::default().fg(Color::Red);
let logs: Vec<ListItem> = app let logs: Vec<ListItem> = app
.logs .logs
.items .items
@ -174,17 +167,17 @@ where
Style::default() Style::default()
.fg(Color::Black) .fg(Color::Black)
.bg(Color::Green) .bg(Color::Green)
.modifier(Modifier::ITALIC), .add_modifier(Modifier::ITALIC),
) )
.label_style(Style::default().fg(Color::Yellow)) .label_style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Green)); .bar_style(Style::default().fg(Color::Green));
f.render_widget(barchart, chunks[1]); f.render_widget(barchart, chunks[1]);
} }
if app.show_chart { if app.show_chart {
let x_labels = vec![ let x_labels = vec![
Span::styled( Span::styled(
format!("{}", app.signals.window[0]), format!("{}", app.signals.window[0]),
StyleDiff::default().modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
), ),
Span::raw(format!( Span::raw(format!(
"{}", "{}",
@ -192,7 +185,7 @@ where
)), )),
Span::styled( Span::styled(
format!("{}", app.signals.window[1]), format!("{}", app.signals.window[1]),
StyleDiff::default().modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
), ),
]; ];
let datasets = vec![ let datasets = vec![
@ -216,9 +209,9 @@ where
Block::default() Block::default()
.title(Span::styled( .title(Span::styled(
"Chart", "Chart",
StyleDiff::default() Style::default()
.fg(Color::Cyan) .fg(Color::Cyan)
.modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
)) ))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
@ -235,9 +228,9 @@ where
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.bounds([-20.0, 20.0]) .bounds([-20.0, 20.0])
.labels(vec![ .labels(vec![
Span::styled("-20", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)),
Span::raw("0"), Span::raw("0"),
Span::styled("20", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("20", Style::default().add_modifier(Modifier::BOLD)),
]), ]),
); );
f.render_widget(chart, chunks[1]); f.render_widget(chart, chunks[1]);
@ -253,22 +246,22 @@ where
Spans::from(""), Spans::from(""),
Spans::from(vec![ Spans::from(vec![
Span::from("For example: "), Span::from("For example: "),
Span::styled("under", StyleDiff::default().fg(Color::Red)), Span::styled("under", Style::default().fg(Color::Red)),
Span::raw(" "), Span::raw(" "),
Span::styled("the", StyleDiff::default().fg(Color::Green)), Span::styled("the", Style::default().fg(Color::Green)),
Span::raw(" "), Span::raw(" "),
Span::styled("rainbow", StyleDiff::default().fg(Color::Blue)), Span::styled("rainbow", Style::default().fg(Color::Blue)),
Span::raw("."), Span::raw("."),
]), ]),
Spans::from(vec![ Spans::from(vec![
Span::raw("Oh and if you didn't "), Span::raw("Oh and if you didn't "),
Span::styled("notice", StyleDiff::default().modifier(Modifier::ITALIC)), Span::styled("notice", Style::default().add_modifier(Modifier::ITALIC)),
Span::raw(" you can "), Span::raw(" you can "),
Span::styled("automatically", StyleDiff::default().modifier(Modifier::BOLD)), Span::styled("automatically", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" "), Span::raw(" "),
Span::styled("wrap", StyleDiff::default().modifier(Modifier::REVERSED)), Span::styled("wrap", Style::default().add_modifier(Modifier::REVERSED)),
Span::raw(" your "), Span::raw(" your "),
Span::styled("text", StyleDiff::default().modifier(Modifier::UNDERLINED)), Span::styled("text", Style::default().add_modifier(Modifier::UNDERLINED)),
Span::raw(".") Span::raw(".")
]), ]),
Spans::from( Spans::from(
@ -277,9 +270,9 @@ where
]; ];
let block = Block::default().borders(Borders::ALL).title(Span::styled( let block = Block::default().borders(Borders::ALL).title(Span::styled(
"Footer", "Footer",
StyleDiff::default() Style::default()
.fg(Color::Magenta) .fg(Color::Magenta)
.modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
)); ));
let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
f.render_widget(paragraph, area); f.render_widget(paragraph, area);
@ -296,7 +289,7 @@ where
let up_style = Style::default().fg(Color::Green); let up_style = Style::default().fg(Color::Green);
let failure_style = Style::default() let failure_style = Style::default()
.fg(Color::Red) .fg(Color::Red)
.modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT); .add_modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT);
let header = ["Server", "Location", "Status"]; let header = ["Server", "Location", "Status"];
let rows = app.servers.iter().map(|s| { let rows = app.servers.iter().map(|s| {
let style = if s.status == "Up" { let style = if s.status == "Up" {

View file

@ -79,28 +79,32 @@ fn main() -> Result<(), Box<dyn Error>> {
let gauge = Gauge::default() let gauge = Gauge::default()
.block(Block::default().title("Gauge1").borders(Borders::ALL)) .block(Block::default().title("Gauge1").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow)) .gauge_style(Style::default().fg(Color::Yellow))
.percent(app.progress1); .percent(app.progress1);
f.render_widget(gauge, chunks[0]); f.render_widget(gauge, chunks[0]);
let label = format!("{}/100", app.progress2); let label = format!("{}/100", app.progress2);
let gauge = Gauge::default() let gauge = Gauge::default()
.block(Block::default().title("Gauge2").borders(Borders::ALL)) .block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Magenta).bg(Color::Green)) .gauge_style(Style::default().fg(Color::Magenta).bg(Color::Green))
.percent(app.progress2) .percent(app.progress2)
.label(label); .label(label);
f.render_widget(gauge, chunks[1]); f.render_widget(gauge, chunks[1]);
let gauge = Gauge::default() let gauge = Gauge::default()
.block(Block::default().title("Gauge3").borders(Borders::ALL)) .block(Block::default().title("Gauge3").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow)) .gauge_style(Style::default().fg(Color::Yellow))
.ratio(app.progress3); .ratio(app.progress3);
f.render_widget(gauge, chunks[2]); f.render_widget(gauge, chunks[2]);
let label = format!("{}/100", app.progress2); let label = format!("{}/100", app.progress2);
let gauge = Gauge::default() let gauge = Gauge::default()
.block(Block::default().title("Gauge4").borders(Borders::ALL)) .block(Block::default().title("Gauge4"))
.style(Style::default().fg(Color::Cyan).modifier(Modifier::ITALIC)) .gauge_style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::ITALIC),
)
.percent(app.progress4) .percent(app.progress4)
.label(label); .label(label);
f.render_widget(gauge, chunks[3]); f.render_widget(gauge, chunks[3]);

View file

@ -10,7 +10,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Constraint, Corner, Direction, Layout}, layout::{Constraint, Corner, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Borders, List, ListItem}, widgets::{Block, Borders, List, ListItem},
Terminal, Terminal,
@ -108,8 +108,6 @@ fn main() -> Result<(), Box<dyn Error>> {
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(f.size()); .split(f.size());
let style = Style::default().fg(Color::Black).bg(Color::White);
let items: Vec<ListItem> = app let items: Vec<ListItem> = app
.items .items
.items .items
@ -119,18 +117,17 @@ fn main() -> Result<(), Box<dyn Error>> {
for _ in 0..i.1 { for _ in 0..i.1 {
lines.push(Spans::from(Span::styled( lines.push(Spans::from(Span::styled(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
StyleDiff::default().modifier(Modifier::ITALIC), Style::default().add_modifier(Modifier::ITALIC),
))); )));
} }
ListItem::new(lines) ListItem::new(lines).style(Style::default().fg(Color::Black).bg(Color::White))
}) })
.collect(); .collect();
let items = List::new(items) let items = List::new(items)
.block(Block::default().borders(Borders::ALL).title("List")) .block(Block::default().borders(Borders::ALL).title("List"))
.style(style) .highlight_style(
.highlight_style_diff( Style::default()
StyleDiff::default() .bg(Color::LightGreen)
.fg(Color::LightGreen)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
) )
.highlight_symbol(">> "); .highlight_symbol(">> ");
@ -141,18 +138,18 @@ fn main() -> Result<(), Box<dyn Error>> {
.iter() .iter()
.map(|&(evt, level)| { .map(|&(evt, level)| {
let s = match level { let s = match level {
"CRITICAL" => StyleDiff::default().fg(Color::Red), "CRITICAL" => Style::default().fg(Color::Red),
"ERROR" => StyleDiff::default().fg(Color::Magenta), "ERROR" => Style::default().fg(Color::Magenta),
"WARNING" => StyleDiff::default().fg(Color::Yellow), "WARNING" => Style::default().fg(Color::Yellow),
"INFO" => StyleDiff::default().fg(Color::Blue), "INFO" => Style::default().fg(Color::Blue),
_ => StyleDiff::default(), _ => Style::default(),
}; };
let header = Spans::from(vec![ let header = Spans::from(vec![
Span::styled(format!("{:<9}", level), s), Span::styled(format!("{:<9}", level), s),
Span::raw(" "), Span::raw(" "),
Span::styled( Span::styled(
"2020-01-01 10:00:00", "2020-01-01 10:00:00",
StyleDiff::default().modifier(Modifier::ITALIC), Style::default().add_modifier(Modifier::ITALIC),
), ),
]); ]);
let log = Spans::from(vec![Span::raw(evt)]); let log = Spans::from(vec![Span::raw(evt)]);

View file

@ -7,7 +7,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout}, layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Borders, Paragraph, Wrap}, widgets::{Block, Borders, Paragraph, Wrap},
Terminal, Terminal,
@ -34,7 +34,7 @@ fn main() -> Result<(), Box<dyn Error>> {
long_line.push('\n'); long_line.push('\n');
let block = Block::default() let block = Block::default()
.style(Style::default().bg(Color::White)); .style(Style::default().bg(Color::White).fg(Color::Black));
f.render_widget(block, size); f.render_widget(block, size);
let chunks = Layout::default() let chunks = Layout::default()
@ -53,40 +53,45 @@ fn main() -> Result<(), Box<dyn Error>> {
let text = vec![ let text = vec![
Spans::from("This is a line "), Spans::from("This is a line "),
Spans::from(Span::styled("This is a line ", StyleDiff::default().fg(Color::Red))), Spans::from(Span::styled("This is a line ", Style::default().fg(Color::Red))),
Spans::from(Span::styled("This is a line", StyleDiff::default().bg(Color::Blue))), Spans::from(Span::styled("This is a line", Style::default().bg(Color::Blue))),
Spans::from(Span::styled( Spans::from(Span::styled(
"This is a longer line", "This is a longer line",
StyleDiff::default().modifier(Modifier::CROSSED_OUT), Style::default().add_modifier(Modifier::CROSSED_OUT),
)), )),
Spans::from(Span::styled(&long_line, StyleDiff::default().bg(Color::Green))), Spans::from(Span::styled(&long_line, Style::default().bg(Color::Green))),
Spans::from(Span::styled( Spans::from(Span::styled(
"This is a line", "This is a line",
StyleDiff::default().fg(Color::Green).modifier(Modifier::ITALIC), Style::default().fg(Color::Green).add_modifier(Modifier::ITALIC),
)), )),
]; ];
let create_block = |title| { let create_block = |title| {
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.title(Span::styled(title, StyleDiff::default().add_modifier(Modifier::BOLD))) .style(Style::default().bg(Color::White).fg(Color::Black))
.title(Span::styled(title, Style::default().add_modifier(Modifier::BOLD)))
}; };
let paragraph = Paragraph::new(text.clone()) let paragraph = Paragraph::new(text.clone())
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Left, no wrap")) .block(create_block("Left, no wrap"))
.alignment(Alignment::Left); .alignment(Alignment::Left);
f.render_widget(paragraph, chunks[0]); f.render_widget(paragraph, chunks[0]);
let paragraph = Paragraph::new(text.clone()) let paragraph = Paragraph::new(text.clone())
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Left, wrap")) .block(create_block("Left, wrap"))
.alignment(Alignment::Left) .alignment(Alignment::Left)
.wrap(Wrap { trim: true }); .wrap(Wrap { trim: true });
f.render_widget(paragraph, chunks[1]); f.render_widget(paragraph, chunks[1]);
let paragraph = Paragraph::new(text.clone()) let paragraph = Paragraph::new(text.clone())
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Center, wrap")) .block(create_block("Center, wrap"))
.alignment(Alignment::Center) .alignment(Alignment::Center)
.wrap(Wrap { trim: true }) .wrap(Wrap { trim: true })
.scroll((scroll, 0)); .scroll((scroll, 0));
f.render_widget(paragraph, chunks[2]); f.render_widget(paragraph, chunks[2]);
let paragraph = Paragraph::new(text) let paragraph = Paragraph::new(text)
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Right, wrap")) .block(create_block("Right, wrap"))
.alignment(Alignment::Right) .alignment(Alignment::Right)
.wrap(Wrap { trim: true }); .wrap(Wrap { trim: true });

View file

@ -7,7 +7,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, StyleDiff}, style::{Color, Modifier, Style},
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Borders, Clear, Paragraph, Wrap}, widgets::{Block, Borders, Clear, Paragraph, Wrap},
Terminal, Terminal,
@ -66,16 +66,16 @@ fn main() -> Result<(), Box<dyn Error>> {
let text = vec![ let text = vec![
Spans::from("This is a line "), Spans::from("This is a line "),
Spans::from(Span::styled("This is a line ", StyleDiff::default().fg(Color::Red))), Spans::from(Span::styled("This is a line ", Style::default().fg(Color::Red))),
Spans::from(Span::styled("This is a line", StyleDiff::default().bg(Color::Blue))), Spans::from(Span::styled("This is a line", Style::default().bg(Color::Blue))),
Spans::from(Span::styled( Spans::from(Span::styled(
"This is a longer line\n", "This is a longer line\n",
StyleDiff::default().modifier(Modifier::CROSSED_OUT), Style::default().add_modifier(Modifier::CROSSED_OUT),
)), )),
Spans::from(Span::styled(&long_line, StyleDiff::default().bg(Color::Green))), Spans::from(Span::styled(&long_line, Style::default().bg(Color::Green))),
Spans::from(Span::styled( Spans::from(Span::styled(
"This is a line\n", "This is a line\n",
StyleDiff::default().fg(Color::Green).modifier(Modifier::ITALIC), Style::default().fg(Color::Green).add_modifier(Modifier::ITALIC),
)), )),
]; ];

View file

@ -93,7 +93,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.margin(5) .margin(5)
.split(f.size()); .split(f.size());
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::BOLD); let selected_style = Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD);
let normal_style = Style::default().fg(Color::White); let normal_style = Style::default().fg(Color::White);
let header = ["Header1", "Header2", "Header3"]; let header = ["Header1", "Header2", "Header3"];
let rows = table let rows = table

View file

@ -10,7 +10,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Borders, Tabs}, widgets::{Block, Borders, Tabs},
Terminal, Terminal,
@ -45,7 +45,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(size); .split(size);
let block = Block::default().style(Style::default().bg(Color::White)); let block = Block::default().style(Style::default().bg(Color::White).fg(Color::Black));
f.render_widget(block, size); f.render_widget(block, size);
let titles = app let titles = app
.tabs .tabs
@ -54,8 +54,8 @@ fn main() -> Result<(), Box<dyn Error>> {
.map(|t| { .map(|t| {
let (first, rest) = t.split_at(1); let (first, rest) = t.split_at(1);
Spans::from(vec![ Spans::from(vec![
Span::styled(first, StyleDiff::default().fg(Color::Yellow)), Span::styled(first, Style::default().fg(Color::Yellow)),
Span::styled(rest, StyleDiff::default().fg(Color::Green)), Span::styled(rest, Style::default().fg(Color::Green)),
]) ])
}) })
.collect(); .collect();
@ -63,7 +63,11 @@ fn main() -> Result<(), Box<dyn Error>> {
.block(Block::default().borders(Borders::ALL).title("Tabs")) .block(Block::default().borders(Borders::ALL).title("Tabs"))
.select(app.tabs.index) .select(app.tabs.index)
.style(Style::default().fg(Color::Cyan)) .style(Style::default().fg(Color::Cyan))
.highlight_style_diff(StyleDiff::default().modifier(Modifier::BOLD)); .highlight_style(
Style::default()
.add_modifier(Modifier::BOLD)
.bg(Color::Black),
);
f.render_widget(tabs, chunks[0]); f.render_widget(tabs, chunks[0]);
let inner = match app.tabs.index { let inner = match app.tabs.index {
0 => Block::default().title("Inner 0").borders(Borders::ALL), 0 => Block::default().title("Inner 0").borders(Borders::ALL),

View file

@ -19,8 +19,8 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{ use tui::{
backend::TermionBackend, backend::TermionBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff}, style::{Color, Modifier, Style},
text::{Span, Spans}, text::{Span, Spans, Text},
widgets::{Block, Borders, List, ListItem, Paragraph}, widgets::{Block, Borders, List, ListItem, Paragraph},
Terminal, Terminal,
}; };
@ -81,29 +81,38 @@ fn main() -> Result<(), Box<dyn Error>> {
) )
.split(f.size()); .split(f.size());
let msg = match app.input_mode { let (msg, style) = match app.input_mode {
InputMode::Normal => vec![ InputMode::Normal => (
Span::raw("Press "), vec![
Span::styled("q", StyleDiff::default().add_modifier(Modifier::BOLD)), Span::raw("Press "),
Span::raw(" to exit, "), Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::styled("e", StyleDiff::default().add_modifier(Modifier::BOLD)), Span::raw(" to exit, "),
Span::raw(" to start editing."), Span::styled("e", Style::default().add_modifier(Modifier::BOLD)),
], Span::raw(" to start editing."),
InputMode::Editing => vec![ ],
Span::raw("Press "), Style::default().add_modifier(Modifier::RAPID_BLINK),
Span::styled("Esc", StyleDiff::default().add_modifier(Modifier::BOLD)), ),
Span::raw(" to stop editing, "), InputMode::Editing => (
Span::styled("Enter", StyleDiff::default().add_modifier(Modifier::BOLD)), vec![
Span::raw(" to record the message"), Span::raw("Press "),
], Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to stop editing, "),
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to record the message"),
],
Style::default(),
),
}; };
let text = vec![Spans::from(msg)]; let mut text = Text::from(Spans::from(msg));
text.patch_style(style);
let help_message = Paragraph::new(text); let help_message = Paragraph::new(text);
f.render_widget(help_message, chunks[0]); f.render_widget(help_message, chunks[0]);
let text = vec![Spans::from(app.input.as_ref())]; let input = Paragraph::new(app.input.as_ref())
let input = Paragraph::new(text) .style(match app.input_mode {
.style(Style::default().fg(Color::Yellow)) InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Input")); .block(Block::default().borders(Borders::ALL).title("Input"));
f.render_widget(input, chunks[1]); f.render_widget(input, chunks[1]);
match app.input_mode { match app.input_mode {

View file

@ -67,7 +67,9 @@ impl Events {
}; };
let tick_handle = { let tick_handle = {
thread::spawn(move || loop { thread::spawn(move || loop {
tx.send(Event::Tick).unwrap(); if tx.send(Event::Tick).is_err() {
break;
}
thread::sleep(config.tick_rate); thread::sleep(config.tick_rate);
}) })
}; };

View file

@ -1,8 +1,9 @@
use std::{ use crate::{
fmt, backend::Backend,
io::{self, Write}, buffer::Cell,
layout::Rect,
style::{Color, Modifier},
}; };
use crossterm::{ use crossterm::{
cursor::{Hide, MoveTo, Show}, cursor::{Hide, MoveTo, Show},
execute, queue, execute, queue,
@ -12,10 +13,10 @@ use crossterm::{
}, },
terminal::{self, Clear, ClearType}, terminal::{self, Clear, ClearType},
}; };
use std::{
use crate::backend::Backend; fmt,
use crate::style::{Color, Modifier}; io::{self, Write},
use crate::{buffer::Cell, layout::Rect, style}; };
pub struct CrosstermBackend<W: Write> { pub struct CrosstermBackend<W: Write> {
buffer: W, buffer: W,
@ -54,41 +55,39 @@ where
use fmt::Write; use fmt::Write;
let mut string = String::with_capacity(content.size_hint().0 * 3); let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut style = style::Style::default(); let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut modifier = Modifier::empty();
let mut last_y = 0; let mut last_y = 0;
let mut last_x = 0; let mut last_x = 0;
let mut inst = 0;
map_error(queue!(string, MoveTo(0, 0)))?;
for (x, y, cell) in content { for (x, y, cell) in content {
if y != last_y || x != last_x + 1 || inst == 0 { if y != last_y || x != last_x + 1 {
map_error(queue!(string, MoveTo(x, y)))?; map_error(queue!(string, MoveTo(x, y)))?;
} }
last_x = x; last_x = x;
last_y = y; last_y = y;
if cell.style.modifier != style.modifier { if cell.modifier != modifier {
let diff = ModifierDiff { let diff = ModifierDiff {
from: style.modifier, from: modifier,
to: cell.style.modifier, to: cell.modifier,
}; };
diff.queue(&mut string)?; diff.queue(&mut string)?;
inst += 1; modifier = cell.modifier;
style.modifier = cell.style.modifier;
} }
if cell.style.fg != style.fg { if cell.fg != fg {
let color = CColor::from(cell.style.fg); let color = CColor::from(cell.fg);
map_error(queue!(string, SetForegroundColor(color)))?; map_error(queue!(string, SetForegroundColor(color)))?;
style.fg = cell.style.fg; fg = cell.fg;
inst += 1;
} }
if cell.style.bg != style.bg { if cell.bg != bg {
let color = CColor::from(cell.style.bg); let color = CColor::from(cell.bg);
map_error(queue!(string, SetBackgroundColor(color)))?; map_error(queue!(string, SetBackgroundColor(color)))?;
style.bg = cell.style.bg; bg = cell.bg;
inst += 1;
} }
string.push_str(&cell.symbol); string.push_str(&cell.symbol);
inst += 1;
} }
map_error(queue!( map_error(queue!(

View file

@ -3,7 +3,7 @@ use std::io;
use crate::backend::Backend; use crate::backend::Backend;
use crate::buffer::Cell; use crate::buffer::Cell;
use crate::layout::Rect; use crate::layout::Rect;
use crate::style::{Color, Modifier, Style}; use crate::style::{Color, Modifier};
use crate::symbols::{bar, block}; use crate::symbols::{bar, block};
#[cfg(unix)] #[cfg(unix)]
use crate::symbols::{line, DOT}; use crate::symbols::{line, DOT};
@ -41,44 +41,41 @@ impl Backend for CursesBackend {
{ {
let mut last_col = 0; let mut last_col = 0;
let mut last_row = 0; let mut last_row = 0;
let mut style = Style { let mut fg = Color::Reset;
fg: Color::Reset, let mut bg = Color::Reset;
bg: Color::Reset, let mut modifier = Modifier::empty();
modifier: Modifier::empty(),
};
let mut curses_style = CursesStyle { let mut curses_style = CursesStyle {
fg: easycurses::Color::White, fg: easycurses::Color::White,
bg: easycurses::Color::Black, bg: easycurses::Color::Black,
}; };
let mut update_color = false; let mut update_color = false;
for (col, row, cell) in content { for (col, row, cell) in content {
// eprintln!("{:?}", cell);
if row != last_row || col != last_col + 1 { if row != last_row || col != last_col + 1 {
self.curses.move_rc(i32::from(row), i32::from(col)); self.curses.move_rc(i32::from(row), i32::from(col));
} }
last_col = col; last_col = col;
last_row = row; last_row = row;
if cell.style.modifier != style.modifier { if cell.modifier != modifier {
apply_modifier_diff(&mut self.curses.win, style.modifier, cell.style.modifier); apply_modifier_diff(&mut self.curses.win, modifier, cell.modifier);
style.modifier = cell.style.modifier; modifier = cell.modifier;
}; };
if cell.style.fg != style.fg { if cell.fg != fg {
update_color = true; update_color = true;
if let Some(ccolor) = cell.style.fg.into() { if let Some(ccolor) = cell.fg.into() {
style.fg = cell.style.fg; fg = cell.fg;
curses_style.fg = ccolor; curses_style.fg = ccolor;
} else { } else {
style.fg = Color::White; fg = Color::White;
curses_style.fg = easycurses::Color::White; curses_style.fg = easycurses::Color::White;
} }
}; };
if cell.style.bg != style.bg { if cell.bg != bg {
update_color = true; update_color = true;
if let Some(ccolor) = cell.style.bg.into() { if let Some(ccolor) = cell.bg.into() {
style.bg = cell.style.bg; bg = cell.bg;
curses_style.bg = ccolor; curses_style.bg = ccolor;
} else { } else {
style.bg = Color::Black; bg = Color::Black;
curses_style.bg = easycurses::Color::Black; curses_style.bg = easycurses::Color::Black;
} }
}; };

View file

@ -34,9 +34,9 @@ impl Backend for RustboxBackend {
self.rustbox.print( self.rustbox.print(
x as usize, x as usize,
y as usize, y as usize,
cell.style.modifier.into(), cell.modifier.into(),
cell.style.fg.into(), cell.fg.into(),
cell.style.bg.into(), cell.bg.into(),
&cell.symbol, &cell.symbol,
); );
} }

View file

@ -3,9 +3,11 @@ use std::io;
use std::io::Write; use std::io::Write;
use super::Backend; use super::Backend;
use crate::buffer::Cell; use crate::{
use crate::layout::Rect; buffer::Cell,
use crate::style; layout::Rect,
style::{Color, Modifier},
};
pub struct TermionBackend<W> pub struct TermionBackend<W>
where where
@ -77,49 +79,46 @@ where
use std::fmt::Write; use std::fmt::Write;
let mut string = String::with_capacity(content.size_hint().0 * 3); let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut style = style::Style::default(); let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut modifier = Modifier::empty();
let mut last_y = 0; let mut last_y = 0;
let mut last_x = 0; let mut last_x = 0;
let mut inst = 0; write!(string, "{}", termion::cursor::Goto(1, 1)).unwrap();
for (x, y, cell) in content { for (x, y, cell) in content {
if y != last_y || x != last_x + 1 || inst == 0 { if y != last_y || x != last_x + 1 {
write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap(); write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
inst += 1;
} }
last_x = x; last_x = x;
last_y = y; last_y = y;
if cell.style.modifier != style.modifier { if cell.modifier != modifier {
write!( write!(
string, string,
"{}", "{}",
ModifierDiff { ModifierDiff {
from: style.modifier, from: modifier,
to: cell.style.modifier to: cell.modifier
} }
) )
.unwrap(); .unwrap();
style.modifier = cell.style.modifier; modifier = cell.modifier;
inst += 1;
} }
if cell.style.fg != style.fg { if cell.fg != fg {
write!(string, "{}", Fg(cell.style.fg)).unwrap(); write!(string, "{}", Fg(cell.fg)).unwrap();
style.fg = cell.style.fg; fg = cell.fg;
inst += 1;
} }
if cell.style.bg != style.bg { if cell.bg != bg {
write!(string, "{}", Bg(cell.style.bg)).unwrap(); write!(string, "{}", Bg(cell.bg)).unwrap();
style.bg = cell.style.bg; bg = cell.bg;
inst += 1;
} }
string.push_str(&cell.symbol); string.push_str(&cell.symbol);
inst += 1;
} }
write!( write!(
self.stdout, self.stdout,
"{}{}{}{}", "{}{}{}{}",
string, string,
Fg(style::Color::Reset), Fg(Color::Reset),
Bg(style::Color::Reset), Bg(Color::Reset),
termion::style::Reset, termion::style::Reset,
) )
} }
@ -135,64 +134,64 @@ where
} }
} }
struct Fg(style::Color); struct Fg(Color);
struct Bg(style::Color); struct Bg(Color);
struct ModifierDiff { struct ModifierDiff {
from: style::Modifier, from: Modifier,
to: style::Modifier, to: Modifier,
} }
impl fmt::Display for Fg { impl fmt::Display for Fg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use termion::color::Color; use termion::color::Color as TermionColor;
match self.0 { match self.0 {
style::Color::Reset => termion::color::Reset.write_fg(f), Color::Reset => termion::color::Reset.write_fg(f),
style::Color::Black => termion::color::Black.write_fg(f), Color::Black => termion::color::Black.write_fg(f),
style::Color::Red => termion::color::Red.write_fg(f), Color::Red => termion::color::Red.write_fg(f),
style::Color::Green => termion::color::Green.write_fg(f), Color::Green => termion::color::Green.write_fg(f),
style::Color::Yellow => termion::color::Yellow.write_fg(f), Color::Yellow => termion::color::Yellow.write_fg(f),
style::Color::Blue => termion::color::Blue.write_fg(f), Color::Blue => termion::color::Blue.write_fg(f),
style::Color::Magenta => termion::color::Magenta.write_fg(f), Color::Magenta => termion::color::Magenta.write_fg(f),
style::Color::Cyan => termion::color::Cyan.write_fg(f), Color::Cyan => termion::color::Cyan.write_fg(f),
style::Color::Gray => termion::color::White.write_fg(f), Color::Gray => termion::color::White.write_fg(f),
style::Color::DarkGray => termion::color::LightBlack.write_fg(f), Color::DarkGray => termion::color::LightBlack.write_fg(f),
style::Color::LightRed => termion::color::LightRed.write_fg(f), Color::LightRed => termion::color::LightRed.write_fg(f),
style::Color::LightGreen => termion::color::LightGreen.write_fg(f), Color::LightGreen => termion::color::LightGreen.write_fg(f),
style::Color::LightBlue => termion::color::LightBlue.write_fg(f), Color::LightBlue => termion::color::LightBlue.write_fg(f),
style::Color::LightYellow => termion::color::LightYellow.write_fg(f), Color::LightYellow => termion::color::LightYellow.write_fg(f),
style::Color::LightMagenta => termion::color::LightMagenta.write_fg(f), Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
style::Color::LightCyan => termion::color::LightCyan.write_fg(f), Color::LightCyan => termion::color::LightCyan.write_fg(f),
style::Color::White => termion::color::LightWhite.write_fg(f), Color::White => termion::color::LightWhite.write_fg(f),
style::Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f), Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f), Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
} }
} }
} }
impl fmt::Display for Bg { impl fmt::Display for Bg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use termion::color::Color; use termion::color::Color as TermionColor;
match self.0 { match self.0 {
style::Color::Reset => termion::color::Reset.write_bg(f), Color::Reset => termion::color::Reset.write_bg(f),
style::Color::Black => termion::color::Black.write_bg(f), Color::Black => termion::color::Black.write_bg(f),
style::Color::Red => termion::color::Red.write_bg(f), Color::Red => termion::color::Red.write_bg(f),
style::Color::Green => termion::color::Green.write_bg(f), Color::Green => termion::color::Green.write_bg(f),
style::Color::Yellow => termion::color::Yellow.write_bg(f), Color::Yellow => termion::color::Yellow.write_bg(f),
style::Color::Blue => termion::color::Blue.write_bg(f), Color::Blue => termion::color::Blue.write_bg(f),
style::Color::Magenta => termion::color::Magenta.write_bg(f), Color::Magenta => termion::color::Magenta.write_bg(f),
style::Color::Cyan => termion::color::Cyan.write_bg(f), Color::Cyan => termion::color::Cyan.write_bg(f),
style::Color::Gray => termion::color::White.write_bg(f), Color::Gray => termion::color::White.write_bg(f),
style::Color::DarkGray => termion::color::LightBlack.write_bg(f), Color::DarkGray => termion::color::LightBlack.write_bg(f),
style::Color::LightRed => termion::color::LightRed.write_bg(f), Color::LightRed => termion::color::LightRed.write_bg(f),
style::Color::LightGreen => termion::color::LightGreen.write_bg(f), Color::LightGreen => termion::color::LightGreen.write_bg(f),
style::Color::LightBlue => termion::color::LightBlue.write_bg(f), Color::LightBlue => termion::color::LightBlue.write_bg(f),
style::Color::LightYellow => termion::color::LightYellow.write_bg(f), Color::LightYellow => termion::color::LightYellow.write_bg(f),
style::Color::LightMagenta => termion::color::LightMagenta.write_bg(f), Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
style::Color::LightCyan => termion::color::LightCyan.write_bg(f), Color::LightCyan => termion::color::LightCyan.write_bg(f),
style::Color::White => termion::color::LightWhite.write_bg(f), Color::White => termion::color::LightWhite.write_bg(f),
style::Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f), Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f), Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
} }
} }
} }
@ -200,63 +199,61 @@ impl fmt::Display for Bg {
impl fmt::Display for ModifierDiff { impl fmt::Display for ModifierDiff {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let remove = self.from - self.to; let remove = self.from - self.to;
if remove.contains(style::Modifier::REVERSED) { if remove.contains(Modifier::REVERSED) {
write!(f, "{}", termion::style::NoInvert)?; write!(f, "{}", termion::style::NoInvert)?;
} }
if remove.contains(style::Modifier::BOLD) { if remove.contains(Modifier::BOLD) {
// XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
// terminals, and NoFaint additionally disables bold... so we use this trick to get // terminals, and NoFaint additionally disables bold... so we use this trick to get
// the right semantics. // the right semantics.
write!(f, "{}", termion::style::NoFaint)?; write!(f, "{}", termion::style::NoFaint)?;
if self.to.contains(style::Modifier::DIM) { if self.to.contains(Modifier::DIM) {
write!(f, "{}", termion::style::Faint)?; write!(f, "{}", termion::style::Faint)?;
} }
} }
if remove.contains(style::Modifier::ITALIC) { if remove.contains(Modifier::ITALIC) {
write!(f, "{}", termion::style::NoItalic)?; write!(f, "{}", termion::style::NoItalic)?;
} }
if remove.contains(style::Modifier::UNDERLINED) { if remove.contains(Modifier::UNDERLINED) {
write!(f, "{}", termion::style::NoUnderline)?; write!(f, "{}", termion::style::NoUnderline)?;
} }
if remove.contains(style::Modifier::DIM) { if remove.contains(Modifier::DIM) {
write!(f, "{}", termion::style::NoFaint)?; write!(f, "{}", termion::style::NoFaint)?;
// XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
// here if we want it. // here if we want it.
if self.to.contains(style::Modifier::BOLD) { if self.to.contains(Modifier::BOLD) {
write!(f, "{}", termion::style::Bold)?; write!(f, "{}", termion::style::Bold)?;
} }
} }
if remove.contains(style::Modifier::CROSSED_OUT) { if remove.contains(Modifier::CROSSED_OUT) {
write!(f, "{}", termion::style::NoCrossedOut)?; write!(f, "{}", termion::style::NoCrossedOut)?;
} }
if remove.contains(style::Modifier::SLOW_BLINK) if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
|| remove.contains(style::Modifier::RAPID_BLINK)
{
write!(f, "{}", termion::style::NoBlink)?; write!(f, "{}", termion::style::NoBlink)?;
} }
let add = self.to - self.from; let add = self.to - self.from;
if add.contains(style::Modifier::REVERSED) { if add.contains(Modifier::REVERSED) {
write!(f, "{}", termion::style::Invert)?; write!(f, "{}", termion::style::Invert)?;
} }
if add.contains(style::Modifier::BOLD) { if add.contains(Modifier::BOLD) {
write!(f, "{}", termion::style::Bold)?; write!(f, "{}", termion::style::Bold)?;
} }
if add.contains(style::Modifier::ITALIC) { if add.contains(Modifier::ITALIC) {
write!(f, "{}", termion::style::Italic)?; write!(f, "{}", termion::style::Italic)?;
} }
if add.contains(style::Modifier::UNDERLINED) { if add.contains(Modifier::UNDERLINED) {
write!(f, "{}", termion::style::Underline)?; write!(f, "{}", termion::style::Underline)?;
} }
if add.contains(style::Modifier::DIM) { if add.contains(Modifier::DIM) {
write!(f, "{}", termion::style::Faint)?; write!(f, "{}", termion::style::Faint)?;
} }
if add.contains(style::Modifier::CROSSED_OUT) { if add.contains(Modifier::CROSSED_OUT) {
write!(f, "{}", termion::style::CrossedOut)?; write!(f, "{}", termion::style::CrossedOut)?;
} }
if add.contains(style::Modifier::SLOW_BLINK) || add.contains(style::Modifier::RAPID_BLINK) { if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
write!(f, "{}", termion::style::Blink)?; write!(f, "{}", termion::style::Blink)?;
} }

View file

@ -106,8 +106,7 @@ impl Backend for TestBackend {
{ {
for (x, y, c) in content { for (x, y, c) in content {
let cell = self.buffer.get_mut(x, y); let cell = self.buffer.get_mut(x, y);
cell.symbol = c.symbol.clone(); *cell = c.clone();
cell.style = c.style;
} }
Ok(()) Ok(())
} }

View file

@ -11,7 +11,9 @@ use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Cell { pub struct Cell {
pub symbol: String, pub symbol: String,
pub style: Style, pub fg: Color,
pub bg: Color,
pub modifier: Modifier,
} }
impl Cell { impl Cell {
@ -28,29 +30,33 @@ impl Cell {
} }
pub fn set_fg(&mut self, color: Color) -> &mut Cell { pub fn set_fg(&mut self, color: Color) -> &mut Cell {
self.style.fg = color; self.fg = color;
self self
} }
pub fn set_bg(&mut self, color: Color) -> &mut Cell { pub fn set_bg(&mut self, color: Color) -> &mut Cell {
self.style.bg = color; self.bg = color;
self
}
pub fn set_modifier(&mut self, modifier: Modifier) -> &mut Cell {
self.style.modifier = modifier;
self self
} }
pub fn set_style(&mut self, style: Style) -> &mut Cell { pub fn set_style(&mut self, style: Style) -> &mut Cell {
self.style = style; if let Some(c) = style.fg {
self.fg = c;
}
if let Some(c) = style.bg {
self.bg = c;
}
self.modifier.insert(style.add_modifier);
self.modifier.remove(style.sub_modifier);
self self
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.symbol.clear(); self.symbol.clear();
self.symbol.push(' '); self.symbol.push(' ');
self.style.reset(); self.fg = Color::Reset;
self.bg = Color::Reset;
self.modifier = Modifier::empty();
} }
} }
@ -58,7 +64,9 @@ impl Default for Cell {
fn default() -> Cell { fn default() -> Cell {
Cell { Cell {
symbol: " ".into(), symbol: " ".into(),
style: Default::default(), fg: Color::Reset,
bg: Color::Reset,
modifier: Modifier::empty(),
} }
} }
} }
@ -83,11 +91,10 @@ impl Default for Cell {
/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White)); /// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
/// assert_eq!(buf.get(5, 0), &Cell{ /// assert_eq!(buf.get(5, 0), &Cell{
/// symbol: String::from("r"), /// symbol: String::from("r"),
/// style: Style { /// fg: Color::Red,
/// fg: Color::Red, /// bg: Color::White,
/// bg: Color::White, /// modifier: Modifier::empty()
/// modifier: Modifier::empty() /// });
/// }});
/// buf.get_mut(5, 0).set_char('x'); /// buf.get_mut(5, 0).set_char('x');
/// assert_eq!(buf.get(5, 0).symbol, "x"); /// assert_eq!(buf.get(5, 0).symbol, "x");
/// ``` /// ```
@ -299,14 +306,7 @@ impl Buffer {
(x_offset as u16, y) (x_offset as u16, y)
} }
pub fn set_spans<'a>( pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
&mut self,
x: u16,
y: u16,
spans: &Spans<'a>,
width: u16,
base_style: Style,
) -> (u16, u16) {
let mut remaining_width = width; let mut remaining_width = width;
let mut x = x; let mut x = x;
for span in &spans.0 { for span in &spans.0 {
@ -318,7 +318,7 @@ impl Buffer {
y, y,
span.content.as_ref(), span.content.as_ref(),
remaining_width as usize, remaining_width as usize,
base_style.patch(span.style_diff), span.style,
); );
let w = pos.0.saturating_sub(x); let w = pos.0.saturating_sub(x);
x = pos.0; x = pos.0;
@ -327,23 +327,14 @@ impl Buffer {
(x, y) (x, y)
} }
pub fn set_span<'a>( pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) {
&mut self, self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
x: u16,
y: u16,
span: &Span<'a>,
width: u16,
base_style: Style,
) -> (u16, u16) {
self.set_stringn(
x,
y,
span.content.as_ref(),
width as usize,
base_style.patch(span.style_diff),
)
} }
#[deprecated(
since = "0.10.0",
note = "You should use styling capabilities of `Buffer::set_style`"
)]
pub fn set_background(&mut self, area: Rect, color: Color) { pub fn set_background(&mut self, area: Rect, color: Color) {
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
for x in area.left()..area.right() { for x in area.left()..area.right() {
@ -352,6 +343,14 @@ impl Buffer {
} }
} }
pub fn set_style(&mut self, area: Rect, style: Style) {
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
self.get_mut(x, y).set_style(style);
}
}
}
/// Resize the buffer so that the mapped area matches the given area and that the buffer /// Resize the buffer so that the mapped area matches the given area and that the buffer
/// length is equal to area.width * area.height /// length is equal to area.width * area.height
pub fn resize(&mut self, area: Rect) { pub fn resize(&mut self, area: Rect) {

View file

@ -58,192 +58,43 @@ bitflags! {
/// ///
/// ```rust /// ```rust
/// # use tui::style::{Color, Modifier, Style}; /// # use tui::style::{Color, Modifier, Style};
/// // Using the raw struct initialization: /// Style::default()
/// let s = Style {
/// fg: Color::Black,
/// bg: Color::Green,
/// modifier: Modifier::ITALIC | Modifier::BOLD
/// };
/// // Using the provided builder pattern:
/// let s = Style::default()
/// .fg(Color::Black) /// .fg(Color::Black)
/// .bg(Color::Green) /// .bg(Color::Green)
/// .modifier(Modifier::ITALIC | Modifier::BOLD); /// .add_modifier(Modifier::ITALIC | Modifier::BOLD);
/// ``` /// ```
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style { pub struct Style {
/// The foreground color. pub fg: Option<Color>,
pub fg: Color, pub bg: Option<Color>,
/// The background color. pub add_modifier: Modifier,
pub bg: Color, pub sub_modifier: Modifier,
/// The emphasis applied to the text graphemes.
pub modifier: Modifier,
} }
impl Default for Style { impl Default for Style {
fn default() -> Style { fn default() -> Style {
Style::new() Style {
fg: None,
bg: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
} }
} }
impl Style { impl Style {
pub const fn new() -> Self {
Style {
fg: Color::Reset,
bg: Color::Reset,
modifier: Modifier::empty(),
}
}
/// Reinitializes the style properties. Both colors are put back to `Color::Reset` while
/// all modifiers are cleared.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style};
/// let mut s = Style::default().fg(Color::Red).bg(Color::Green).modifier(Modifier::BOLD);
/// s.reset();
/// assert_eq!(s.fg, Color::Reset);
/// assert_eq!(s.bg, Color::Reset);
/// assert_eq!(s.modifier, Modifier::empty());
/// ```
pub fn reset(&mut self) {
self.fg = Color::Reset;
self.bg = Color::Reset;
self.modifier = Modifier::empty();
}
/// Changes the foreground color. /// Changes the foreground color.
/// ///
/// ## Examples /// ## Examples
/// ///
/// ```rust /// ```rust
/// # use tui::style::{Color, Style}; /// # use tui::style::{Color, Style};
/// let s = Style::default().fg(Color::Red);
/// assert_eq!(s.fg, Color::Red);
/// ```
pub const fn fg(mut self, color: Color) -> Style {
self.fg = color;
self
}
/// Changes the background color.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Style};
/// let s = Style::default().bg(Color::Red);
/// assert_eq!(s.bg, Color::Red);
/// ```
pub const fn bg(mut self, color: Color) -> Style {
self.bg = color;
self
}
/// Changes the emphasis.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Modifier, Style};
/// let s = Style::default().modifier(Modifier::BOLD | Modifier::ITALIC);
/// assert_eq!(s.modifier, Modifier::BOLD | Modifier::ITALIC);
/// ```
pub const fn modifier(mut self, modifier: Modifier) -> Style {
self.modifier = modifier;
self
}
/// Creates a new [`Style`] by applying the given diff to its properties.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// let style = Style::default().fg(Color::Green).bg(Color::Black).modifier(Modifier::BOLD);
///
/// let diff = StyleDiff::default();
/// let patched = style.patch(diff);
/// assert_eq!(patched, style);
///
/// let diff = StyleDiff::default().fg(Color::Blue).add_modifier(Modifier::ITALIC);
/// let patched = style.patch(diff);
/// assert_eq!(
/// patched,
/// Style {
/// fg: Color::Blue,
/// bg: Color::Black,
/// modifier: Modifier::BOLD | Modifier::ITALIC,
/// }
/// );
/// ```
pub fn patch(mut self, diff: StyleDiff) -> Style {
if let Some(c) = diff.fg {
self.fg = c;
}
if let Some(c) = diff.bg {
self.bg = c;
}
if let Some(m) = diff.modifier {
self.modifier = m;
}
self.modifier.insert(diff.add_modifier);
self.modifier.remove(diff.sub_modifier);
self
}
}
/// StyleDiff is a set of updates that can be applied to a [`Style`] to get a
/// new one.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StyleDiff {
fg: Option<Color>,
bg: Option<Color>,
modifier: Option<Modifier>,
add_modifier: Modifier,
sub_modifier: Modifier,
}
impl Default for StyleDiff {
fn default() -> StyleDiff {
StyleDiff {
fg: None,
bg: None,
modifier: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
}
impl From<Style> for StyleDiff {
fn from(s: Style) -> StyleDiff {
StyleDiff {
fg: Some(s.fg),
bg: Some(s.bg),
modifier: Some(s.modifier),
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
}
impl StyleDiff {
/// Changes the foreground color.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Style, StyleDiff};
/// let style = Style::default().fg(Color::Blue); /// let style = Style::default().fg(Color::Blue);
/// let diff = StyleDiff::default().fg(Color::Red); /// let diff = Style::default().fg(Color::Red);
/// assert_eq!(style.patch(diff), Style::default().fg(Color::Red)); /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
/// ``` /// ```
pub fn fg(mut self, color: Color) -> StyleDiff { pub fn fg(mut self, color: Color) -> Style {
self.fg = Some(color); self.fg = Some(color);
self self
} }
@ -253,48 +104,31 @@ impl StyleDiff {
/// ## Examples /// ## Examples
/// ///
/// ```rust /// ```rust
/// # use tui::style::{Color, Style, StyleDiff}; /// # use tui::style::{Color, Style};
/// let style = Style::default().bg(Color::Blue); /// let style = Style::default().bg(Color::Blue);
/// let diff = StyleDiff::default().bg(Color::Red); /// let diff = Style::default().bg(Color::Red);
/// assert_eq!(style.patch(diff), Style::default().bg(Color::Red)); /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
/// ``` /// ```
pub fn bg(mut self, color: Color) -> StyleDiff { pub fn bg(mut self, color: Color) -> Style {
self.bg = Some(color); self.bg = Some(color);
self self
} }
/// Changes the text emphasis. /// Changes the text emphasis.
/// ///
/// When applied, it replaces the `Style` modifier with the given value. /// When applied, it adds the given modifier to the `Style` modifiers.
/// ///
/// ## Examples /// ## Examples
/// ///
/// ```rust /// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff}; /// # use tui::style::{Color, Modifier, Style};
/// let style = Style::default().modifier(Modifier::BOLD); /// let style = Style::default().add_modifier(Modifier::BOLD);
/// let diff = StyleDiff::default().modifier(Modifier::ITALIC); /// let diff = Style::default().add_modifier(Modifier::ITALIC);
/// assert_eq!(style.patch(diff), Style::default().modifier(Modifier::ITALIC)); /// let patched = style.patch(diff);
/// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
/// assert_eq!(patched.sub_modifier, Modifier::empty());
/// ``` /// ```
pub fn modifier(mut self, modifier: Modifier) -> StyleDiff { pub fn add_modifier(mut self, modifier: Modifier) -> Style {
self.add_modifier = Modifier::empty();
self.sub_modifier = Modifier::empty();
self.modifier = Some(modifier);
self
}
/// Changes the text emphasis.
///
/// When applied, it adds the given modifiers to the `Style` modifier.
///
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// let style = Style::default().modifier(Modifier::BOLD);
/// let diff = StyleDiff::default().add_modifier(Modifier::ITALIC);
/// assert_eq!(style.patch(diff), Style::default().modifier(Modifier::BOLD | Modifier::ITALIC));
/// ```
pub fn add_modifier(mut self, modifier: Modifier) -> StyleDiff {
self.sub_modifier.remove(modifier); self.sub_modifier.remove(modifier);
self.add_modifier.insert(modifier); self.add_modifier.insert(modifier);
self self
@ -302,51 +136,46 @@ impl StyleDiff {
/// Changes the text emphasis. /// Changes the text emphasis.
/// ///
/// When applied, it removes the given modifiers from the `Style` modifier. /// When applied, it removes the given modifier from the `Style` modifiers.
/// ///
/// ## Examples /// ## Examples
/// ///
/// ```rust /// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff}; /// # use tui::style::{Color, Modifier, Style};
/// let style = Style::default().modifier(Modifier::BOLD | Modifier::ITALIC); /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
/// let diff = StyleDiff::default().remove_modifier(Modifier::ITALIC); /// let diff = Style::default().remove_modifier(Modifier::ITALIC);
/// assert_eq!(style.patch(diff), Style::default().modifier(Modifier::BOLD)); /// let patched = style.patch(diff);
/// assert_eq!(patched.add_modifier, Modifier::BOLD);
/// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
/// ``` /// ```
pub fn remove_modifier(mut self, modifier: Modifier) -> StyleDiff { pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
self.add_modifier.remove(modifier); self.add_modifier.remove(modifier);
self.sub_modifier.insert(modifier); self.sub_modifier.insert(modifier);
self self
} }
/// Results in a combined style diff that is equivalent to applying the two individual diffs to /// Results in a combined style that is equivalent to applying the two individual styles to
/// a style one after the other. /// a style one after the other.
/// ///
/// ## Examples /// ## Examples
/// ``` /// ```
/// # use tui::style::{Color, Modifier, Style, StyleDiff}; /// # use tui::style::{Color, Modifier, Style};
/// let style_1 = StyleDiff::default().fg(Color::Yellow); /// let style_1 = Style::default().fg(Color::Yellow);
/// let style_2 = StyleDiff::default().bg(Color::Red); /// let style_2 = Style::default().bg(Color::Red);
/// let combined = style_1.patch(style_2); /// let combined = style_1.patch(style_2);
/// assert_eq!( /// assert_eq!(
/// Style::default().patch(style_1).patch(style_2), /// Style::default().patch(style_1).patch(style_2),
/// Style::default().patch(combined)); /// Style::default().patch(combined));
/// ``` /// ```
pub fn patch(mut self, other: StyleDiff) -> StyleDiff { pub fn patch(mut self, other: Style) -> Style {
self.fg = other.fg.or(self.fg); self.fg = other.fg.or(self.fg);
self.bg = other.bg.or(self.bg); self.bg = other.bg.or(self.bg);
self.modifier = other.modifier.or(self.modifier);
// If the other is about to specify a full modifier, it would fully override whatever self.add_modifier.remove(other.sub_modifier);
// add/sub modifiers the current style wants to apply so ignore those in that case. self.add_modifier.insert(other.add_modifier);
if other.modifier.is_some() { self.sub_modifier.remove(other.add_modifier);
self.add_modifier = other.add_modifier; self.sub_modifier.insert(other.sub_modifier);
self.sub_modifier = other.sub_modifier;
} else {
self.add_modifier.remove(other.sub_modifier);
self.add_modifier.insert(other.add_modifier);
self.sub_modifier.remove(other.add_modifier);
self.sub_modifier.insert(other.sub_modifier);
}
self self
} }
} }
@ -355,30 +184,27 @@ impl StyleDiff {
mod tests { mod tests {
use super::*; use super::*;
fn diffs() -> Vec<StyleDiff> { fn styles() -> Vec<Style> {
vec![ vec![
StyleDiff::default(), Style::default(),
StyleDiff::default().fg(Color::Yellow), Style::default().fg(Color::Yellow),
StyleDiff::default().bg(Color::Yellow), Style::default().bg(Color::Yellow),
StyleDiff::default().modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
StyleDiff::default().modifier(Modifier::ITALIC), Style::default().remove_modifier(Modifier::BOLD),
StyleDiff::default().modifier(Modifier::ITALIC | Modifier::BOLD), Style::default().add_modifier(Modifier::ITALIC),
StyleDiff::default().add_modifier(Modifier::BOLD), Style::default().remove_modifier(Modifier::ITALIC),
StyleDiff::default().remove_modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
StyleDiff::default().add_modifier(Modifier::ITALIC), Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
StyleDiff::default().remove_modifier(Modifier::ITALIC),
StyleDiff::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
StyleDiff::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
] ]
} }
#[test] #[test]
fn combined_patch_gives_same_result_as_individual_patch() { fn combined_patch_gives_same_result_as_individual_patch() {
let diffs = diffs(); let styles = styles();
for &a in &diffs { for &a in &styles {
for &b in &diffs { for &b in &styles {
for &c in &diffs { for &c in &styles {
for &d in &diffs { for &d in &styles {
let combined = a.patch(b.patch(c.patch(d))); let combined = a.patch(b.patch(c.patch(d)));
assert_eq!( assert_eq!(
@ -390,29 +216,4 @@ mod tests {
} }
} }
} }
#[test]
fn diffs_respect_later_modifiers() {
let diffs = diffs();
for &a in &diffs {
for &b in &diffs {
let random_diff = a.patch(b);
let set_bold = random_diff.modifier(Modifier::BOLD);
assert_eq!(Style::default().patch(set_bold).modifier, Modifier::BOLD);
let add_bold = random_diff.add_modifier(Modifier::BOLD);
assert!(Style::default()
.patch(add_bold)
.modifier
.contains(Modifier::BOLD));
let remove_bold = random_diff.remove_modifier(Modifier::BOLD);
assert!(!Style::default()
.patch(remove_bold)
.modifier
.contains(Modifier::BOLD));
}
}
}
} }

View file

@ -21,32 +21,32 @@
//! ```rust //! ```rust
//! # use tui::widgets::Block; //! # use tui::widgets::Block;
//! # use tui::text::{Span, Spans}; //! # use tui::text::{Span, Spans};
//! # use tui::style::{Color, StyleDiff}; //! # use tui::style::{Color, Style};
//! // A simple string with no styling. //! // A simple string with no styling.
//! // Converted to Spans(vec![ //! // Converted to Spans(vec![
//! // Span { content: Cow::Borrowed("My title"), style_diff: StyleDiff { .. } } //! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
//! // ]) //! // ])
//! let block = Block::default().title("My title"); //! let block = Block::default().title("My title");
//! //!
//! // A simple string with a unique style. //! // A simple string with a unique style.
//! // Converted to Spans(vec![ //! // Converted to Spans(vec![
//! // Span { content: Cow::Borrowed("My title"), style_diff: StyleDiff { fg: Some(Color::Yellow), .. } //! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
//! // ]) //! // ])
//! let block = Block::default().title( //! let block = Block::default().title(
//! Span::styled("My title", StyleDiff::default().fg(Color::Yellow)) //! Span::styled("My title", Style::default().fg(Color::Yellow))
//! ); //! );
//! //!
//! // A string with multiple styles. //! // A string with multiple styles.
//! // Converted to Spans(vec![ //! // Converted to Spans(vec![
//! // Span { content: Cow::Borrowed("My"), style_diff: StyleDiff { fg: Some(Color::Yellow), .. } }, //! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
//! // Span { content: Cow::Borrowed(" title"), .. } //! // Span { content: Cow::Borrowed(" title"), .. }
//! // ]) //! // ])
//! let block = Block::default().title(vec![ //! let block = Block::default().title(vec![
//! Span::styled("My", StyleDiff::default().fg(Color::Yellow)), //! Span::styled("My", Style::default().fg(Color::Yellow)),
//! Span::raw(" title"), //! Span::raw(" title"),
//! ]); //! ]);
//! ``` //! ```
use crate::style::{Style, StyleDiff}; use crate::style::Style;
use std::{borrow::Cow, cmp::max}; use std::{borrow::Cow, cmp::max};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -62,7 +62,7 @@ pub struct StyledGrapheme<'a> {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Span<'a> { pub struct Span<'a> {
pub content: Cow<'a, str>, pub content: Cow<'a, str>,
pub style_diff: StyleDiff, pub style: Style,
} }
impl<'a> Span<'a> { impl<'a> Span<'a> {
@ -81,7 +81,7 @@ impl<'a> Span<'a> {
{ {
Span { Span {
content: content.into(), content: content.into(),
style_diff: StyleDiff::default(), style: Style::default(),
} }
} }
@ -91,18 +91,18 @@ impl<'a> Span<'a> {
/// ///
/// ```rust /// ```rust
/// # use tui::text::Span; /// # use tui::text::Span;
/// # use tui::style::{Color, Modifier, StyleDiff}; /// # use tui::style::{Color, Modifier, Style};
/// let style = StyleDiff::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC); /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
/// Span::styled("My text", style); /// Span::styled("My text", style);
/// Span::styled(String::from("My text"), style); /// Span::styled(String::from("My text"), style);
/// ``` /// ```
pub fn styled<T>(content: T, style_diff: StyleDiff) -> Span<'a> pub fn styled<T>(content: T, style: Style) -> Span<'a>
where where
T: Into<Cow<'a, str>>, T: Into<Cow<'a, str>>,
{ {
Span { Span {
content: content.into(), content: content.into(),
style_diff, style,
} }
} }
@ -113,17 +113,17 @@ impl<'a> Span<'a> {
/// Returns an iterator over the graphemes held by this span. /// Returns an iterator over the graphemes held by this span.
/// ///
/// `base_style` is the [`Style`] that will be patched with each grapheme [`StyleDiff`] to get /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
/// the resulting [`Style`]. /// the resulting [`Style`].
/// ///
/// ## Examples /// ## Examples
/// ///
/// ```rust /// ```rust
/// # use tui::text::{Span, StyledGrapheme}; /// # use tui::text::{Span, StyledGrapheme};
/// # use tui::style::{Color, Modifier, Style, StyleDiff}; /// # use tui::style::{Color, Modifier, Style};
/// # use std::iter::Iterator; /// # use std::iter::Iterator;
/// let style_diff = StyleDiff::default().fg(Color::Yellow); /// let style = Style::default().fg(Color::Yellow);
/// let span = Span::styled("Text", style_diff); /// let span = Span::styled("Text", style);
/// let style = Style::default().fg(Color::Green).bg(Color::Black); /// let style = Style::default().fg(Color::Green).bg(Color::Black);
/// let styled_graphemes = span.styled_graphemes(style); /// let styled_graphemes = span.styled_graphemes(style);
/// assert_eq!( /// assert_eq!(
@ -131,33 +131,37 @@ impl<'a> Span<'a> {
/// StyledGrapheme { /// StyledGrapheme {
/// symbol: "T", /// symbol: "T",
/// style: Style { /// style: Style {
/// fg: Color::Yellow, /// fg: Some(Color::Yellow),
/// bg: Color::Black, /// bg: Some(Color::Black),
/// modifier: Modifier::empty(), /// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// }, /// },
/// }, /// },
/// StyledGrapheme { /// StyledGrapheme {
/// symbol: "e", /// symbol: "e",
/// style: Style { /// style: Style {
/// fg: Color::Yellow, /// fg: Some(Color::Yellow),
/// bg: Color::Black, /// bg: Some(Color::Black),
/// modifier: Modifier::empty(), /// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// }, /// },
/// }, /// },
/// StyledGrapheme { /// StyledGrapheme {
/// symbol: "x", /// symbol: "x",
/// style: Style { /// style: Style {
/// fg: Color::Yellow, /// fg: Some(Color::Yellow),
/// bg: Color::Black, /// bg: Some(Color::Black),
/// modifier: Modifier::empty(), /// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// }, /// },
/// }, /// },
/// StyledGrapheme { /// StyledGrapheme {
/// symbol: "t", /// symbol: "t",
/// style: Style { /// style: Style {
/// fg: Color::Yellow, /// fg: Some(Color::Yellow),
/// bg: Color::Black, /// bg: Some(Color::Black),
/// modifier: Modifier::empty(), /// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// }, /// },
/// }, /// },
/// ], /// ],
@ -171,7 +175,7 @@ impl<'a> Span<'a> {
UnicodeSegmentation::graphemes(self.content.as_ref(), true) UnicodeSegmentation::graphemes(self.content.as_ref(), true)
.map(move |g| StyledGrapheme { .map(move |g| StyledGrapheme {
symbol: g, symbol: g,
style: base_style.patch(self.style_diff), style: base_style.patch(self.style),
}) })
.filter(|s| s.symbol != "\n") .filter(|s| s.symbol != "\n")
} }
@ -206,9 +210,9 @@ impl<'a> Spans<'a> {
/// ///
/// ```rust /// ```rust
/// # use tui::text::{Span, Spans}; /// # use tui::text::{Span, Spans};
/// # use tui::style::{Color, StyleDiff}; /// # use tui::style::{Color, Style};
/// let spans = Spans::from(vec![ /// let spans = Spans::from(vec![
/// Span::styled("My", StyleDiff::default().fg(Color::Yellow)), /// Span::styled("My", Style::default().fg(Color::Yellow)),
/// Span::raw(" text"), /// Span::raw(" text"),
/// ]); /// ]);
/// assert_eq!(7, spans.width()); /// assert_eq!(7, spans.width());
@ -290,6 +294,14 @@ impl<'a> Text<'a> {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.lines.len() self.lines.len()
} }
pub fn patch_style(&mut self, style: Style) {
for line in &mut self.lines {
for span in &mut line.0 {
span.style = span.style.patch(style);
}
}
}
} }
impl<'a> From<&'a str> for Text<'a> { impl<'a> From<&'a str> for Text<'a> {
@ -300,6 +312,20 @@ impl<'a> From<&'a str> for Text<'a> {
} }
} }
impl<'a> From<Span<'a>> for Text<'a> {
fn from(span: Span<'a>) -> Text<'a> {
Text {
lines: vec![Spans::from(span)],
}
}
}
impl<'a> From<Spans<'a>> for Text<'a> {
fn from(spans: Spans<'a>) -> Text<'a> {
Text { lines: vec![spans] }
}
}
impl<'a> From<Vec<Spans<'a>>> for Text<'a> { impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
fn from(lines: Vec<Spans<'a>>) -> Text<'a> { fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
Text { lines } Text { lines }

View file

@ -19,8 +19,8 @@ use unicode_width::UnicodeWidthStr;
/// .block(Block::default().title("BarChart").borders(Borders::ALL)) /// .block(Block::default().title("BarChart").borders(Borders::ALL))
/// .bar_width(3) /// .bar_width(3)
/// .bar_gap(1) /// .bar_gap(1)
/// .style(Style::default().fg(Color::Yellow).bg(Color::Red)) /// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
/// .value_style(Style::default().fg(Color::Red).modifier(Modifier::BOLD)) /// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
/// .label_style(Style::default().fg(Color::White)) /// .label_style(Style::default().fg(Color::White))
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)]) /// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
/// .max(4); /// .max(4);
@ -35,6 +35,8 @@ pub struct BarChart<'a> {
bar_gap: u16, bar_gap: u16,
/// Set of symbols used to display the data /// Set of symbols used to display the data
bar_set: symbols::bar::Set, bar_set: symbols::bar::Set,
/// Style of the bars
bar_style: Style,
/// Style of the values printed at the bottom of each bar /// Style of the values printed at the bottom of each bar
value_style: Style, value_style: Style,
/// Style of the labels printed under each bar /// Style of the labels printed under each bar
@ -57,6 +59,7 @@ impl<'a> Default for BarChart<'a> {
max: None, max: None,
data: &[], data: &[],
values: Vec::new(), values: Vec::new(),
bar_style: Style::default(),
bar_width: 1, bar_width: 1,
bar_gap: 1, bar_gap: 1,
bar_set: symbols::bar::NINE_LEVELS, bar_set: symbols::bar::NINE_LEVELS,
@ -87,6 +90,11 @@ impl<'a> BarChart<'a> {
self self
} }
pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
self.bar_style = style;
self
}
pub fn bar_width(mut self, width: u16) -> BarChart<'a> { pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
self.bar_width = width; self.bar_width = width;
self self
@ -120,6 +128,8 @@ impl<'a> BarChart<'a> {
impl<'a> Widget for BarChart<'a> { impl<'a> Widget for BarChart<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) { fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let chart_area = match self.block.take() { let chart_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -133,8 +143,6 @@ impl<'a> Widget for BarChart<'a> {
return; return;
} }
buf.set_background(chart_area, self.style.bg);
let max = self let max = self
.max .max
.unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc))); .unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
@ -173,7 +181,7 @@ impl<'a> Widget for BarChart<'a> {
chart_area.top() + j, chart_area.top() + j,
) )
.set_symbol(symbol) .set_symbol(symbol)
.set_style(self.style); .set_style(self.bar_style);
} }
if d.1 > 8 { if d.1 > 8 {

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::{Style, StyleDiff}, style::Style,
symbols::line, symbols::line,
text::{Span, Spans}, text::{Span, Spans},
widgets::{Borders, Widget}, widgets::{Borders, Widget},
@ -84,7 +84,7 @@ impl<'a> Block<'a> {
pub fn title_style(mut self, style: Style) -> Block<'a> { pub fn title_style(mut self, style: Style) -> Block<'a> {
if let Some(t) = self.title { if let Some(t) = self.title {
let title = String::from(t); let title = String::from(t);
self.title = Some(Spans::from(Span::styled(title, StyleDiff::from(style)))); self.title = Some(Spans::from(Span::styled(title, style)));
} }
self self
} }
@ -135,12 +135,12 @@ impl<'a> Block<'a> {
impl<'a> Widget for Block<'a> { impl<'a> Widget for Block<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
if area.width < 2 || area.height < 2 { if area.width < 2 || area.height < 2 {
return; return;
} }
buf.set_background(area, self.style.bg);
let symbols = BorderType::line_symbols(self.border_type); let symbols = BorderType::line_symbols(self.border_type);
// Sides // Sides
if self.borders.intersects(Borders::LEFT) { if self.borders.intersects(Borders::LEFT) {
@ -208,7 +208,7 @@ impl<'a> Widget for Block<'a> {
0 0
}; };
let width = area.width - lx - rx; let width = area.width - lx - rx;
buf.set_spans(area.left() + lx, area.top(), &title, width, self.style); buf.set_spans(area.left() + lx, area.top(), &title, width);
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Rect}, layout::{Constraint, Rect},
style::{Style, StyleDiff}, style::{Color, Style},
symbols, symbols,
text::{Span, Spans}, text::{Span, Spans},
widgets::{ widgets::{
@ -52,7 +52,7 @@ impl<'a> Axis<'a> {
pub fn title_style(mut self, style: Style) -> Axis<'a> { pub fn title_style(mut self, style: Style) -> Axis<'a> {
if let Some(t) = self.title { if let Some(t) = self.title {
let title = String::from(t); let title = String::from(t);
self.title = Some(Spans::from(Span::styled(title, StyleDiff::from(style)))); self.title = Some(Spans::from(Span::styled(title, style)));
} }
self self
} }
@ -183,7 +183,7 @@ impl Default for ChartLayout {
/// ``` /// ```
/// # use tui::symbols; /// # use tui::symbols;
/// # use tui::widgets::{Block, Borders, Chart, Axis, Dataset, GraphType}; /// # use tui::widgets::{Block, Borders, Chart, Axis, Dataset, GraphType};
/// # use tui::style::{Style, StyleDiff, Color}; /// # use tui::style::{Style, Color};
/// # use tui::text::Span; /// # use tui::text::Span;
/// let datasets = vec![ /// let datasets = vec![
/// Dataset::default() /// Dataset::default()
@ -202,12 +202,12 @@ impl Default for ChartLayout {
/// Chart::new(datasets) /// Chart::new(datasets)
/// .block(Block::default().title("Chart")) /// .block(Block::default().title("Chart"))
/// .x_axis(Axis::default() /// .x_axis(Axis::default()
/// .title(Span::styled("X Axis", StyleDiff::default().fg(Color::Red))) /// .title(Span::styled("X Axis", Style::default().fg(Color::Red)))
/// .style(Style::default().fg(Color::White)) /// .style(Style::default().fg(Color::White))
/// .bounds([0.0, 10.0]) /// .bounds([0.0, 10.0])
/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect())) /// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()))
/// .y_axis(Axis::default() /// .y_axis(Axis::default()
/// .title(Span::styled("Y Axis", StyleDiff::default().fg(Color::Red))) /// .title(Span::styled("Y Axis", Style::default().fg(Color::Red)))
/// .style(Style::default().fg(Color::White)) /// .style(Style::default().fg(Color::White))
/// .bounds([0.0, 10.0]) /// .bounds([0.0, 10.0])
/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect())); /// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()));
@ -369,6 +369,7 @@ impl<'a> Chart<'a> {
impl<'a> Widget for Chart<'a> { impl<'a> Widget for Chart<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) { fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let chart_area = match self.block.take() { let chart_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -384,28 +385,14 @@ impl<'a> Widget for Chart<'a> {
return; return;
} }
buf.set_background(chart_area, self.style.bg);
if let Some((x, y)) = layout.title_x { if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap(); let title = self.x_axis.title.unwrap();
buf.set_spans( buf.set_spans(x, y, &title, graph_area.right().saturating_sub(x));
x,
y,
&title,
graph_area.right().saturating_sub(x),
self.style,
);
} }
if let Some((x, y)) = layout.title_y { if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap(); let title = self.y_axis.title.unwrap();
buf.set_spans( buf.set_spans(x, y, &title, graph_area.right().saturating_sub(x));
x,
y,
&title,
graph_area.right().saturating_sub(x),
self.style,
);
} }
if let Some(y) = layout.label_x { if let Some(y) = layout.label_x {
@ -420,7 +407,6 @@ impl<'a> Widget for Chart<'a> {
y, y,
label, label,
label.width() as u16, label.width() as u16,
self.style,
); );
} }
} }
@ -432,13 +418,7 @@ impl<'a> Widget for Chart<'a> {
for (i, label) in labels.iter().enumerate() { for (i, label) in labels.iter().enumerate() {
let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1); let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
if dy < graph_area.bottom() { if dy < graph_area.bottom() {
buf.set_span( buf.set_span(x, graph_area.bottom() - 1 - dy, label, label.width() as u16);
x,
graph_area.bottom() - 1 - dy,
label,
label.width() as u16,
self.style,
);
} }
} }
} }
@ -469,14 +449,14 @@ impl<'a> Widget for Chart<'a> {
for dataset in &self.datasets { for dataset in &self.datasets {
Canvas::default() Canvas::default()
.background_color(self.style.bg) .background_color(self.style.bg.unwrap_or(Color::Reset))
.x_bounds(self.x_axis.bounds) .x_bounds(self.x_axis.bounds)
.y_bounds(self.y_axis.bounds) .y_bounds(self.y_axis.bounds)
.marker(dataset.marker) .marker(dataset.marker)
.paint(|ctx| { .paint(|ctx| {
ctx.draw(&Points { ctx.draw(&Points {
coords: dataset.data, coords: dataset.data,
color: dataset.style.fg, color: dataset.style.fg.unwrap_or(Color::Reset),
}); });
if let GraphType::Line = dataset.graph_type { if let GraphType::Line = dataset.graph_type {
for data in dataset.data.windows(2) { for data in dataset.data.windows(2) {
@ -485,7 +465,7 @@ impl<'a> Widget for Chart<'a> {
y1: data[0].1, y1: data[0].1,
x2: data[1].0, x2: data[1].0,
y2: data[1].1, y2: data[1].1,
color: dataset.style.fg, color: dataset.style.fg.unwrap_or(Color::Reset),
}) })
} }
} }

View file

@ -15,7 +15,7 @@ use crate::{
/// # use tui::style::{Style, Color, Modifier}; /// # use tui::style::{Style, Color, Modifier};
/// Gauge::default() /// Gauge::default()
/// .block(Block::default().borders(Borders::ALL).title("Progress")) /// .block(Block::default().borders(Borders::ALL).title("Progress"))
/// .style(Style::default().fg(Color::White).bg(Color::Black).modifier(Modifier::ITALIC)) /// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::ITALIC))
/// .percent(20); /// .percent(20);
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -24,6 +24,7 @@ pub struct Gauge<'a> {
ratio: f64, ratio: f64,
label: Option<Span<'a>>, label: Option<Span<'a>>,
style: Style, style: Style,
gauge_style: Style,
} }
impl<'a> Default for Gauge<'a> { impl<'a> Default for Gauge<'a> {
@ -32,7 +33,8 @@ impl<'a> Default for Gauge<'a> {
block: None, block: None,
ratio: 0.0, ratio: 0.0,
label: None, label: None,
style: Default::default(), style: Style::default(),
gauge_style: Style::default(),
} }
} }
} }
@ -74,10 +76,16 @@ impl<'a> Gauge<'a> {
self.style = style; self.style = style;
self self
} }
pub fn gauge_style(mut self, style: Style) -> Gauge<'a> {
self.gauge_style = style;
self
}
} }
impl<'a> Widget for Gauge<'a> { impl<'a> Widget for Gauge<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) { fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let gauge_area = match self.block.take() { let gauge_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -86,14 +94,11 @@ impl<'a> Widget for Gauge<'a> {
} }
None => area, None => area,
}; };
buf.set_style(gauge_area, self.gauge_style);
if gauge_area.height < 1 { if gauge_area.height < 1 {
return; return;
} }
if self.style.bg != Color::Reset {
buf.set_background(gauge_area, self.style.bg);
}
let center = gauge_area.height / 2 + gauge_area.top(); let center = gauge_area.height / 2 + gauge_area.top();
let width = (f64::from(gauge_area.width) * self.ratio).round() as u16; let width = (f64::from(gauge_area.width) * self.ratio).round() as u16;
let end = gauge_area.left() + width; let end = gauge_area.left() + width;
@ -111,14 +116,14 @@ impl<'a> Widget for Gauge<'a> {
if y == center { if y == center {
let label_width = label.width() as u16; let label_width = label.width() as u16;
let middle = (gauge_area.width - label_width) / 2 + gauge_area.left(); let middle = (gauge_area.width - label_width) / 2 + gauge_area.left();
buf.set_span(middle, y, &label, gauge_area.right() - middle, self.style); buf.set_span(middle, y, &label, gauge_area.right() - middle);
} }
// Fix colors // Fix colors
for x in gauge_area.left()..end { for x in gauge_area.left()..end {
buf.get_mut(x, y) buf.get_mut(x, y)
.set_fg(self.style.bg) .set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
.set_bg(self.style.fg); .set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
buffer::Buffer, buffer::Buffer,
layout::{Corner, Rect}, layout::{Corner, Rect},
style::{Style, StyleDiff}, style::Style,
text::Text, text::Text,
widgets::{Block, StatefulWidget, Widget}, widgets::{Block, StatefulWidget, Widget},
}; };
@ -39,7 +39,7 @@ impl ListState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ListItem<'a> { pub struct ListItem<'a> {
content: Text<'a>, content: Text<'a>,
style_diff: StyleDiff, style: Style,
} }
impl<'a> ListItem<'a> { impl<'a> ListItem<'a> {
@ -49,12 +49,12 @@ impl<'a> ListItem<'a> {
{ {
ListItem { ListItem {
content: content.into(), content: content.into(),
style_diff: StyleDiff::default(), style: Style::default(),
} }
} }
pub fn style_diff(mut self, style_diff: StyleDiff) -> ListItem<'a> { pub fn style(mut self, style: Style) -> ListItem<'a> {
self.style_diff = style_diff; self.style = style;
self self
} }
@ -69,12 +69,12 @@ impl<'a> ListItem<'a> {
/// ///
/// ``` /// ```
/// # use tui::widgets::{Block, Borders, List, ListItem}; /// # use tui::widgets::{Block, Borders, List, ListItem};
/// # use tui::style::{Style, StyleDiff, Color, Modifier}; /// # use tui::style::{Style, Color, Modifier};
/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")]; /// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")];
/// List::new(items) /// List::new(items)
/// .block(Block::default().title("List").borders(Borders::ALL)) /// .block(Block::default().title("List").borders(Borders::ALL))
/// .style(Style::default().fg(Color::White)) /// .style(Style::default().fg(Color::White))
/// .highlight_style_diff(StyleDiff::default().modifier(Modifier::ITALIC)) /// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
/// .highlight_symbol(">>"); /// .highlight_symbol(">>");
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -85,7 +85,7 @@ pub struct List<'a> {
style: Style, style: Style,
start_corner: Corner, start_corner: Corner,
/// Style used to render selected item /// Style used to render selected item
highlight_style_diff: StyleDiff, highlight_style: Style,
/// Symbol in front of the selected item (Shift all items to the right) /// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol: Option<&'a str>, highlight_symbol: Option<&'a str>,
} }
@ -100,7 +100,7 @@ impl<'a> List<'a> {
style: Style::default(), style: Style::default(),
items: items.into(), items: items.into(),
start_corner: Corner::TopLeft, start_corner: Corner::TopLeft,
highlight_style_diff: StyleDiff::default(), highlight_style: Style::default(),
highlight_symbol: None, highlight_symbol: None,
} }
} }
@ -120,8 +120,8 @@ impl<'a> List<'a> {
self self
} }
pub fn highlight_style_diff(mut self, diff: StyleDiff) -> List<'a> { pub fn highlight_style(mut self, style: Style) -> List<'a> {
self.highlight_style_diff = diff; self.highlight_style = style;
self self
} }
@ -135,6 +135,7 @@ impl<'a> StatefulWidget for List<'a> {
type State = ListState; type State = ListState;
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
buf.set_style(area, self.style);
let list_area = match self.block.take() { let list_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -148,8 +149,6 @@ impl<'a> StatefulWidget for List<'a> {
return; return;
} }
buf.set_background(list_area, self.style.bg);
if self.items.is_empty() { if self.items.is_empty() {
return; return;
} }
@ -191,6 +190,7 @@ impl<'a> StatefulWidget for List<'a> {
.collect::<String>(); .collect::<String>();
let mut current_height = 0; let mut current_height = 0;
let has_selection = state.selected.is_some();
for (i, item) in self for (i, item) in self
.items .items
.iter_mut() .iter_mut()
@ -215,40 +215,27 @@ impl<'a> StatefulWidget for List<'a> {
width: list_area.width, width: list_area.width,
height: item.height() as u16, height: item.height() as u16,
}; };
let item_style = self.style.patch(item.style_diff); let item_style = self.style.patch(item.style);
buf.set_background(area, item_style.bg); buf.set_style(area, item_style);
let elem_x = if let Some(s) = state.selected {
if s == i { let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
for line in &mut item.content.lines { let elem_x = if has_selection {
for span in &mut line.0 { let symbol = if is_selected {
span.style_diff = span.style_diff.patch(self.highlight_style_diff); highlight_symbol
}
}
let (x, _) = buf.set_stringn(
x,
y,
highlight_symbol,
list_area.width as usize,
item_style.patch(self.highlight_style_diff),
);
x
} else { } else {
let (x, _) = &blank_symbol
buf.set_stringn(x, y, &blank_symbol, list_area.width as usize, item_style); };
x let (x, _) = buf.set_stringn(x, y, symbol, list_area.width as usize, item_style);
} x
} else { } else {
x x
}; };
let max_element_width = (list_area.width - (elem_x - x)) as usize; let max_element_width = (list_area.width - (elem_x - x)) as usize;
for (j, line) in item.content.lines.iter().enumerate() { for (j, line) in item.content.lines.iter().enumerate() {
buf.set_spans( buf.set_spans(elem_x, y + j as u16, line, max_element_width as u16);
elem_x, }
y + j as u16, if is_selected {
line, buf.set_style(area, self.highlight_style);
max_element_width as u16,
self.style,
);
} }
} }
} }

View file

@ -26,15 +26,15 @@ fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment)
/// ``` /// ```
/// # use tui::text::{Text, Spans, Span}; /// # use tui::text::{Text, Spans, Span};
/// # use tui::widgets::{Block, Borders, Paragraph, Wrap}; /// # use tui::widgets::{Block, Borders, Paragraph, Wrap};
/// # use tui::style::{Style, StyleDiff, Color, Modifier}; /// # use tui::style::{Style, Color, Modifier};
/// # use tui::layout::{Alignment}; /// # use tui::layout::{Alignment};
/// let text = vec![ /// let text = vec![
/// Spans::from(vec![ /// Spans::from(vec![
/// Span::raw("First"), /// Span::raw("First"),
/// Span::styled("line",StyleDiff::default().add_modifier(Modifier::ITALIC)), /// Span::styled("line",Style::default().add_modifier(Modifier::ITALIC)),
/// Span::raw("."), /// Span::raw("."),
/// ]), /// ]),
/// Spans::from(Span::styled("Second line", StyleDiff::default().fg(Color::Red))), /// Spans::from(Span::styled("Second line", Style::default().fg(Color::Red))),
/// ]; /// ];
/// Paragraph::new(text) /// Paragraph::new(text)
/// .block(Block::default().title("Paragraph").borders(Borders::ALL)) /// .block(Block::default().title("Paragraph").borders(Borders::ALL))
@ -134,6 +134,7 @@ impl<'a> Paragraph<'a> {
impl<'a> Widget for Paragraph<'a> { impl<'a> Widget for Paragraph<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) { fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let text_area = match self.block.take() { let text_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -147,8 +148,6 @@ impl<'a> Widget for Paragraph<'a> {
return; return;
} }
buf.set_background(text_area, self.style.bg);
let style = self.style; let style = self.style;
let mut styled = self.text.lines.iter().flat_map(|spans| { let mut styled = self.text.lines.iter().flat_map(|spans| {
spans spans

View file

@ -121,8 +121,7 @@ impl<'a> Widget for Sparkline<'a> {
}; };
buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j) buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
.set_symbol(symbol) .set_symbol(symbol)
.set_fg(self.style.fg) .set_style(self.style);
.set_bg(self.style.bg);
if *d > 8 { if *d > 8 {
*d -= 8; *d -= 8;

View file

@ -220,6 +220,8 @@ where
type State = TableState; type State = TableState;
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
buf.set_style(area, self.style);
// Render block if necessary and get the drawing area // Render block if necessary and get the drawing area
let table_area = match self.block.take() { let table_area = match self.block.take() {
Some(b) => { Some(b) => {
@ -230,8 +232,6 @@ where
None => area, None => area,
}; };
buf.set_background(table_area, self.style.bg);
let mut solver = Solver::new(); let mut solver = Solver::new();
let mut var_indices = HashMap::new(); let mut var_indices = HashMap::new();
let mut ccs = Vec::new(); let mut ccs = Vec::new();

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::{Style, StyleDiff}, style::Style,
symbols, symbols,
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Widget}, widgets::{Block, Widget},
@ -33,8 +33,8 @@ pub struct Tabs<'a> {
selected: usize, selected: usize,
/// The style used to draw the text /// The style used to draw the text
style: Style, style: Style,
/// Style diff to apply to the selected item /// Style to apply to the selected item
highlight_style_diff: StyleDiff, highlight_style: Style,
/// Tab divider /// Tab divider
divider: Span<'a>, divider: Span<'a>,
} }
@ -46,7 +46,7 @@ impl<'a> Tabs<'a> {
titles, titles,
selected: 0, selected: 0,
style: Default::default(), style: Default::default(),
highlight_style_diff: Default::default(), highlight_style: Default::default(),
divider: Span::raw(symbols::line::VERTICAL), divider: Span::raw(symbols::line::VERTICAL),
} }
} }
@ -66,14 +66,8 @@ impl<'a> Tabs<'a> {
self self
} }
#[deprecated(since = "0.10.0", note = "You should use `Tabs::highlight_style_diff`")]
pub fn highlight_style(mut self, style: Style) -> Tabs<'a> { pub fn highlight_style(mut self, style: Style) -> Tabs<'a> {
self.highlight_style_diff = StyleDiff::from(style); self.highlight_style = style;
self
}
pub fn highlight_style_diff(mut self, diff: StyleDiff) -> Tabs<'a> {
self.highlight_style_diff = diff;
self self
} }
@ -88,6 +82,7 @@ impl<'a> Tabs<'a> {
impl<'a> Widget for Tabs<'a> { impl<'a> Widget for Tabs<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) { fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let tabs_area = match self.block.take() { let tabs_area = match self.block.take() {
Some(b) => { Some(b) => {
let inner_area = b.inner(area); let inner_area = b.inner(area);
@ -101,35 +96,33 @@ impl<'a> Widget for Tabs<'a> {
return; return;
} }
buf.set_background(tabs_area, self.style.bg);
let mut x = tabs_area.left(); let mut x = tabs_area.left();
let titles_length = self.titles.len(); let titles_length = self.titles.len();
for (i, mut title) in self.titles.into_iter().enumerate() { for (i, title) in self.titles.into_iter().enumerate() {
let last_title = titles_length - 1 == i; let last_title = titles_length - 1 == i;
if i == self.selected {
for span in &mut title.0 {
span.style_diff = span.style_diff.patch(self.highlight_style_diff);
}
}
x = x.saturating_add(1); x = x.saturating_add(1);
let remaining_width = tabs_area.right().saturating_sub(x); let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 { if remaining_width == 0 {
break; break;
} }
let pos = buf.set_spans(x, tabs_area.top(), &title, remaining_width, self.style); let pos = buf.set_spans(x, tabs_area.top(), &title, remaining_width);
if i == self.selected {
buf.set_style(
Rect {
x,
y: tabs_area.top(),
width: pos.0.saturating_sub(x),
height: 1,
},
self.highlight_style,
);
}
x = pos.0.saturating_add(1); x = pos.0.saturating_add(1);
let remaining_width = tabs_area.right().saturating_sub(x); let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 || last_title { if remaining_width == 0 || last_title {
break; break;
} }
let pos = buf.set_span( let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
x,
tabs_area.top(),
&self.divider,
remaining_width,
self.style,
);
x = pos.0; x = pos.0;
} }
} }

View file

@ -2,7 +2,7 @@ use tui::{
backend::TestBackend, backend::TestBackend,
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::{Color, StyleDiff}, style::{Color, Style},
text::Span, text::Span,
widgets::{Block, Borders}, widgets::{Block, Borders},
Terminal, Terminal,
@ -15,10 +15,7 @@ fn widgets_block_renders() {
terminal terminal
.draw(|f| { .draw(|f| {
let block = Block::default() let block = Block::default()
.title(Span::styled( .title(Span::styled("Title", Style::default().fg(Color::LightBlue)))
"Title",
StyleDiff::default().fg(Color::LightBlue),
))
.borders(Borders::ALL); .borders(Borders::ALL);
f.render_widget( f.render_widget(
block, block,

View file

@ -2,7 +2,7 @@ use tui::{
backend::TestBackend, backend::TestBackend,
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::{Color, StyleDiff}, style::{Color, Style},
symbols, symbols,
widgets::{Block, Borders, List, ListItem, ListState}, widgets::{Block, Borders, List, ListItem, ListState},
Terminal, Terminal,
@ -23,13 +23,13 @@ fn widgets_list_should_highlight_the_selected_item() {
ListItem::new("Item 3"), ListItem::new("Item 3"),
]; ];
let list = List::new(items) let list = List::new(items)
.highlight_style_diff(StyleDiff::default().bg(Color::Yellow)) .highlight_style(Style::default().bg(Color::Yellow))
.highlight_symbol(">> "); .highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state); f.render_stateful_widget(list, size, &mut state);
}) })
.unwrap(); .unwrap();
let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]); let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]);
for x in 0..9 { for x in 0..10 {
expected.get_mut(x, 1).set_bg(Color::Yellow); expected.get_mut(x, 1).set_bg(Color::Yellow);
} }
terminal.backend().assert_buffer(&expected); terminal.backend().assert_buffer(&expected);