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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{
backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, StyleDiff},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, Clear, Paragraph, Wrap},
Terminal,
@ -66,16 +66,16 @@ fn main() -> Result<(), Box<dyn Error>> {
let text = vec![
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", StyleDiff::default().bg(Color::Blue))),
Spans::from(Span::styled("This is a line ", Style::default().fg(Color::Red))),
Spans::from(Span::styled("This is a line", Style::default().bg(Color::Blue))),
Spans::from(Span::styled(
"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(
"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)
.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 header = ["Header1", "Header2", "Header3"];
let rows = table

View file

@ -10,7 +10,7 @@ use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::Altern
use tui::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, Tabs},
Terminal,
@ -45,7 +45,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.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);
let titles = app
.tabs
@ -54,8 +54,8 @@ fn main() -> Result<(), Box<dyn Error>> {
.map(|t| {
let (first, rest) = t.split_at(1);
Spans::from(vec![
Span::styled(first, StyleDiff::default().fg(Color::Yellow)),
Span::styled(rest, StyleDiff::default().fg(Color::Green)),
Span::styled(first, Style::default().fg(Color::Yellow)),
Span::styled(rest, Style::default().fg(Color::Green)),
])
})
.collect();
@ -63,7 +63,11 @@ fn main() -> Result<(), Box<dyn Error>> {
.block(Block::default().borders(Borders::ALL).title("Tabs"))
.select(app.tabs.index)
.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]);
let inner = match app.tabs.index {
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::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style, StyleDiff},
text::{Span, Spans},
style::{Color, Modifier, Style},
text::{Span, Spans, Text},
widgets::{Block, Borders, List, ListItem, Paragraph},
Terminal,
};
@ -81,29 +81,38 @@ fn main() -> Result<(), Box<dyn Error>> {
)
.split(f.size());
let msg = match app.input_mode {
InputMode::Normal => vec![
Span::raw("Press "),
Span::styled("q", StyleDiff::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "),
Span::styled("e", StyleDiff::default().add_modifier(Modifier::BOLD)),
Span::raw(" to start editing."),
],
InputMode::Editing => vec![
Span::raw("Press "),
Span::styled("Esc", StyleDiff::default().add_modifier(Modifier::BOLD)),
Span::raw(" to stop editing, "),
Span::styled("Enter", StyleDiff::default().add_modifier(Modifier::BOLD)),
Span::raw(" to record the message"),
],
let (msg, style) = match app.input_mode {
InputMode::Normal => (
vec![
Span::raw("Press "),
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "),
Span::styled("e", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to start editing."),
],
Style::default().add_modifier(Modifier::RAPID_BLINK),
),
InputMode::Editing => (
vec![
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);
f.render_widget(help_message, chunks[0]);
let text = vec![Spans::from(app.input.as_ref())];
let input = Paragraph::new(text)
.style(Style::default().fg(Color::Yellow))
let input = Paragraph::new(app.input.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Input"));
f.render_widget(input, chunks[1]);
match app.input_mode {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,9 @@ use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub symbol: String,
pub style: Style,
pub fg: Color,
pub bg: Color,
pub modifier: Modifier,
}
impl Cell {
@ -28,29 +30,33 @@ impl Cell {
}
pub fn set_fg(&mut self, color: Color) -> &mut Cell {
self.style.fg = color;
self.fg = color;
self
}
pub fn set_bg(&mut self, color: Color) -> &mut Cell {
self.style.bg = color;
self
}
pub fn set_modifier(&mut self, modifier: Modifier) -> &mut Cell {
self.style.modifier = modifier;
self.bg = color;
self
}
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
}
pub fn reset(&mut self) {
self.symbol.clear();
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 {
Cell {
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));
/// assert_eq!(buf.get(5, 0), &Cell{
/// symbol: String::from("r"),
/// style: Style {
/// fg: Color::Red,
/// bg: Color::White,
/// modifier: Modifier::empty()
/// }});
/// fg: Color::Red,
/// bg: Color::White,
/// modifier: Modifier::empty()
/// });
/// buf.get_mut(5, 0).set_char('x');
/// assert_eq!(buf.get(5, 0).symbol, "x");
/// ```
@ -299,14 +306,7 @@ impl Buffer {
(x_offset as u16, y)
}
pub fn set_spans<'a>(
&mut self,
x: u16,
y: u16,
spans: &Spans<'a>,
width: u16,
base_style: Style,
) -> (u16, u16) {
pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
let mut remaining_width = width;
let mut x = x;
for span in &spans.0 {
@ -318,7 +318,7 @@ impl Buffer {
y,
span.content.as_ref(),
remaining_width as usize,
base_style.patch(span.style_diff),
span.style,
);
let w = pos.0.saturating_sub(x);
x = pos.0;
@ -327,23 +327,14 @@ impl Buffer {
(x, y)
}
pub fn set_span<'a>(
&mut self,
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),
)
pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) {
self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
}
#[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) {
for y in area.top()..area.bottom() {
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
/// length is equal to area.width * area.height
pub fn resize(&mut self, area: Rect) {

View file

@ -58,192 +58,43 @@ bitflags! {
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style};
/// // Using the raw struct initialization:
/// let s = Style {
/// fg: Color::Black,
/// bg: Color::Green,
/// modifier: Modifier::ITALIC | Modifier::BOLD
/// };
/// // Using the provided builder pattern:
/// let s = Style::default()
/// Style::default()
/// .fg(Color::Black)
/// .bg(Color::Green)
/// .modifier(Modifier::ITALIC | Modifier::BOLD);
/// .add_modifier(Modifier::ITALIC | Modifier::BOLD);
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style {
/// The foreground color.
pub fg: Color,
/// The background color.
pub bg: Color,
/// The emphasis applied to the text graphemes.
pub modifier: Modifier,
pub fg: Option<Color>,
pub bg: Option<Color>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
impl Default for Style {
fn default() -> Style {
Style::new()
Style {
fg: None,
bg: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
}
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.
///
/// ## Examples
///
/// ```rust
/// # 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 diff = StyleDiff::default().fg(Color::Red);
/// let 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
}
@ -253,48 +104,31 @@ impl StyleDiff {
/// ## Examples
///
/// ```rust
/// # use tui::style::{Color, Style, StyleDiff};
/// # use tui::style::{Color, Style};
/// 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));
/// ```
pub fn bg(mut self, color: Color) -> StyleDiff {
pub fn bg(mut self, color: Color) -> Style {
self.bg = Some(color);
self
}
/// 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
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// let style = Style::default().modifier(Modifier::BOLD);
/// let diff = StyleDiff::default().modifier(Modifier::ITALIC);
/// assert_eq!(style.patch(diff), Style::default().modifier(Modifier::ITALIC));
/// # use tui::style::{Color, Modifier, Style};
/// let style = Style::default().add_modifier(Modifier::BOLD);
/// let diff = Style::default().add_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 {
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 {
pub fn add_modifier(mut self, modifier: Modifier) -> Style {
self.sub_modifier.remove(modifier);
self.add_modifier.insert(modifier);
self
@ -302,51 +136,46 @@ impl StyleDiff {
/// 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
///
/// ```rust
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// let style = Style::default().modifier(Modifier::BOLD | Modifier::ITALIC);
/// let diff = StyleDiff::default().remove_modifier(Modifier::ITALIC);
/// assert_eq!(style.patch(diff), Style::default().modifier(Modifier::BOLD));
/// # use tui::style::{Color, Modifier, Style};
/// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
/// let diff = Style::default().remove_modifier(Modifier::ITALIC);
/// 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.sub_modifier.insert(modifier);
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.
///
/// ## Examples
/// ```
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// let style_1 = StyleDiff::default().fg(Color::Yellow);
/// let style_2 = StyleDiff::default().bg(Color::Red);
/// # use tui::style::{Color, Modifier, Style};
/// let style_1 = Style::default().fg(Color::Yellow);
/// let style_2 = Style::default().bg(Color::Red);
/// let combined = style_1.patch(style_2);
/// assert_eq!(
/// Style::default().patch(style_1).patch(style_2),
/// 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.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
// add/sub modifiers the current style wants to apply so ignore those in that case.
if other.modifier.is_some() {
self.add_modifier = other.add_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.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
}
}
@ -355,30 +184,27 @@ impl StyleDiff {
mod tests {
use super::*;
fn diffs() -> Vec<StyleDiff> {
fn styles() -> Vec<Style> {
vec![
StyleDiff::default(),
StyleDiff::default().fg(Color::Yellow),
StyleDiff::default().bg(Color::Yellow),
StyleDiff::default().modifier(Modifier::BOLD),
StyleDiff::default().modifier(Modifier::ITALIC),
StyleDiff::default().modifier(Modifier::ITALIC | Modifier::BOLD),
StyleDiff::default().add_modifier(Modifier::BOLD),
StyleDiff::default().remove_modifier(Modifier::BOLD),
StyleDiff::default().add_modifier(Modifier::ITALIC),
StyleDiff::default().remove_modifier(Modifier::ITALIC),
StyleDiff::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
StyleDiff::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
Style::default(),
Style::default().fg(Color::Yellow),
Style::default().bg(Color::Yellow),
Style::default().add_modifier(Modifier::BOLD),
Style::default().remove_modifier(Modifier::BOLD),
Style::default().add_modifier(Modifier::ITALIC),
Style::default().remove_modifier(Modifier::ITALIC),
Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
]
}
#[test]
fn combined_patch_gives_same_result_as_individual_patch() {
let diffs = diffs();
for &a in &diffs {
for &b in &diffs {
for &c in &diffs {
for &d in &diffs {
let styles = styles();
for &a in &styles {
for &b in &styles {
for &c in &styles {
for &d in &styles {
let combined = a.patch(b.patch(c.patch(d)));
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
//! # use tui::widgets::Block;
//! # use tui::text::{Span, Spans};
//! # use tui::style::{Color, StyleDiff};
//! # use tui::style::{Color, Style};
//! // A simple string with no styling.
//! // 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");
//!
//! // A simple string with a unique style.
//! // 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(
//! Span::styled("My title", StyleDiff::default().fg(Color::Yellow))
//! Span::styled("My title", Style::default().fg(Color::Yellow))
//! );
//!
//! // A string with multiple styles.
//! // 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"), .. }
//! // ])
//! 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"),
//! ]);
//! ```
use crate::style::{Style, StyleDiff};
use crate::style::Style;
use std::{borrow::Cow, cmp::max};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
@ -62,7 +62,7 @@ pub struct StyledGrapheme<'a> {
#[derive(Debug, Clone, PartialEq)]
pub struct Span<'a> {
pub content: Cow<'a, str>,
pub style_diff: StyleDiff,
pub style: Style,
}
impl<'a> Span<'a> {
@ -81,7 +81,7 @@ impl<'a> Span<'a> {
{
Span {
content: content.into(),
style_diff: StyleDiff::default(),
style: Style::default(),
}
}
@ -91,18 +91,18 @@ impl<'a> Span<'a> {
///
/// ```rust
/// # use tui::text::Span;
/// # use tui::style::{Color, Modifier, StyleDiff};
/// let style = StyleDiff::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
/// # use tui::style::{Color, Modifier, Style};
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
/// Span::styled("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
T: Into<Cow<'a, str>>,
{
Span {
content: content.into(),
style_diff,
style,
}
}
@ -113,17 +113,17 @@ impl<'a> Span<'a> {
/// 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`].
///
/// ## Examples
///
/// ```rust
/// # use tui::text::{Span, StyledGrapheme};
/// # use tui::style::{Color, Modifier, Style, StyleDiff};
/// # use tui::style::{Color, Modifier, Style};
/// # use std::iter::Iterator;
/// let style_diff = StyleDiff::default().fg(Color::Yellow);
/// let span = Span::styled("Text", style_diff);
/// let style = Style::default().fg(Color::Yellow);
/// let span = Span::styled("Text", style);
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
/// let styled_graphemes = span.styled_graphemes(style);
/// assert_eq!(
@ -131,33 +131,37 @@ impl<'a> Span<'a> {
/// StyledGrapheme {
/// symbol: "T",
/// style: Style {
/// fg: Color::Yellow,
/// bg: Color::Black,
/// modifier: Modifier::empty(),
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "e",
/// style: Style {
/// fg: Color::Yellow,
/// bg: Color::Black,
/// modifier: Modifier::empty(),
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "x",
/// style: Style {
/// fg: Color::Yellow,
/// bg: Color::Black,
/// modifier: Modifier::empty(),
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// StyledGrapheme {
/// symbol: "t",
/// style: Style {
/// fg: Color::Yellow,
/// bg: Color::Black,
/// modifier: Modifier::empty(),
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// },
/// ],
@ -171,7 +175,7 @@ impl<'a> Span<'a> {
UnicodeSegmentation::graphemes(self.content.as_ref(), true)
.map(move |g| StyledGrapheme {
symbol: g,
style: base_style.patch(self.style_diff),
style: base_style.patch(self.style),
})
.filter(|s| s.symbol != "\n")
}
@ -206,9 +210,9 @@ impl<'a> Spans<'a> {
///
/// ```rust
/// # use tui::text::{Span, Spans};
/// # use tui::style::{Color, StyleDiff};
/// # use tui::style::{Color, Style};
/// let spans = Spans::from(vec![
/// Span::styled("My", StyleDiff::default().fg(Color::Yellow)),
/// Span::styled("My", Style::default().fg(Color::Yellow)),
/// Span::raw(" text"),
/// ]);
/// assert_eq!(7, spans.width());
@ -290,6 +294,14 @@ impl<'a> Text<'a> {
pub fn height(&self) -> usize {
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> {
@ -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> {
fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
Text { lines }

View file

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

View file

@ -1,7 +1,7 @@
use crate::{
buffer::Buffer,
layout::Rect,
style::{Style, StyleDiff},
style::Style,
symbols::line,
text::{Span, Spans},
widgets::{Borders, Widget},
@ -84,7 +84,7 @@ impl<'a> Block<'a> {
pub fn title_style(mut self, style: Style) -> Block<'a> {
if let Some(t) = self.title {
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
}
@ -135,12 +135,12 @@ impl<'a> Block<'a> {
impl<'a> Widget for Block<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
if area.width < 2 || area.height < 2 {
return;
}
buf.set_background(area, self.style.bg);
let symbols = BorderType::line_symbols(self.border_type);
// Sides
if self.borders.intersects(Borders::LEFT) {
@ -208,7 +208,7 @@ impl<'a> Widget for Block<'a> {
0
};
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::{
buffer::Buffer,
layout::{Constraint, Rect},
style::{Style, StyleDiff},
style::{Color, Style},
symbols,
text::{Span, Spans},
widgets::{
@ -52,7 +52,7 @@ impl<'a> Axis<'a> {
pub fn title_style(mut self, style: Style) -> Axis<'a> {
if let Some(t) = self.title {
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
}
@ -183,7 +183,7 @@ impl Default for ChartLayout {
/// ```
/// # use tui::symbols;
/// # use tui::widgets::{Block, Borders, Chart, Axis, Dataset, GraphType};
/// # use tui::style::{Style, StyleDiff, Color};
/// # use tui::style::{Style, Color};
/// # use tui::text::Span;
/// let datasets = vec![
/// Dataset::default()
@ -202,12 +202,12 @@ impl Default for ChartLayout {
/// Chart::new(datasets)
/// .block(Block::default().title("Chart"))
/// .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))
/// .bounds([0.0, 10.0])
/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()))
/// .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))
/// .bounds([0.0, 10.0])
/// .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> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let chart_area = match self.block.take() {
Some(b) => {
let inner_area = b.inner(area);
@ -384,28 +385,14 @@ impl<'a> Widget for Chart<'a> {
return;
}
buf.set_background(chart_area, self.style.bg);
if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap();
buf.set_spans(
x,
y,
&title,
graph_area.right().saturating_sub(x),
self.style,
);
buf.set_spans(x, y, &title, graph_area.right().saturating_sub(x));
}
if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap();
buf.set_spans(
x,
y,
&title,
graph_area.right().saturating_sub(x),
self.style,
);
buf.set_spans(x, y, &title, graph_area.right().saturating_sub(x));
}
if let Some(y) = layout.label_x {
@ -420,7 +407,6 @@ impl<'a> Widget for Chart<'a> {
y,
label,
label.width() as u16,
self.style,
);
}
}
@ -432,13 +418,7 @@ impl<'a> Widget for Chart<'a> {
for (i, label) in labels.iter().enumerate() {
let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
if dy < graph_area.bottom() {
buf.set_span(
x,
graph_area.bottom() - 1 - dy,
label,
label.width() as u16,
self.style,
);
buf.set_span(x, graph_area.bottom() - 1 - dy, label, label.width() as u16);
}
}
}
@ -469,14 +449,14 @@ impl<'a> Widget for Chart<'a> {
for dataset in &self.datasets {
Canvas::default()
.background_color(self.style.bg)
.background_color(self.style.bg.unwrap_or(Color::Reset))
.x_bounds(self.x_axis.bounds)
.y_bounds(self.y_axis.bounds)
.marker(dataset.marker)
.paint(|ctx| {
ctx.draw(&Points {
coords: dataset.data,
color: dataset.style.fg,
color: dataset.style.fg.unwrap_or(Color::Reset),
});
if let GraphType::Line = dataset.graph_type {
for data in dataset.data.windows(2) {
@ -485,7 +465,7 @@ impl<'a> Widget for Chart<'a> {
y1: data[0].1,
x2: data[1].0,
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};
/// Gauge::default()
/// .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);
/// ```
#[derive(Debug, Clone)]
@ -24,6 +24,7 @@ pub struct Gauge<'a> {
ratio: f64,
label: Option<Span<'a>>,
style: Style,
gauge_style: Style,
}
impl<'a> Default for Gauge<'a> {
@ -32,7 +33,8 @@ impl<'a> Default for Gauge<'a> {
block: None,
ratio: 0.0,
label: None,
style: Default::default(),
style: Style::default(),
gauge_style: Style::default(),
}
}
}
@ -74,10 +76,16 @@ impl<'a> Gauge<'a> {
self.style = style;
self
}
pub fn gauge_style(mut self, style: Style) -> Gauge<'a> {
self.gauge_style = style;
self
}
}
impl<'a> Widget for Gauge<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let gauge_area = match self.block.take() {
Some(b) => {
let inner_area = b.inner(area);
@ -86,14 +94,11 @@ impl<'a> Widget for Gauge<'a> {
}
None => area,
};
buf.set_style(gauge_area, self.gauge_style);
if gauge_area.height < 1 {
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 width = (f64::from(gauge_area.width) * self.ratio).round() as u16;
let end = gauge_area.left() + width;
@ -111,14 +116,14 @@ impl<'a> Widget for Gauge<'a> {
if y == center {
let label_width = label.width() as u16;
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
for x in gauge_area.left()..end {
buf.get_mut(x, y)
.set_fg(self.style.bg)
.set_bg(self.style.fg);
.set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
.set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
}
}
}

View file

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

View file

@ -220,6 +220,8 @@ where
type State = TableState;
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
let table_area = match self.block.take() {
Some(b) => {
@ -230,8 +232,6 @@ where
None => area,
};
buf.set_background(table_area, self.style.bg);
let mut solver = Solver::new();
let mut var_indices = HashMap::new();
let mut ccs = Vec::new();

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use tui::{
backend::TestBackend,
buffer::Buffer,
layout::Rect,
style::{Color, StyleDiff},
style::{Color, Style},
symbols,
widgets::{Block, Borders, List, ListItem, ListState},
Terminal,
@ -23,13 +23,13 @@ fn widgets_list_should_highlight_the_selected_item() {
ListItem::new("Item 3"),
];
let list = List::new(items)
.highlight_style_diff(StyleDiff::default().bg(Color::Yellow))
.highlight_style(Style::default().bg(Color::Yellow))
.highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
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);
}
terminal.backend().assert_buffer(&expected);