20 KiB
Changelog
To be released
v0.14.0 - 2021-01-01
Breaking changes
New API for the Table widget
The Table
widget got a lot of improvements that should make it easier to work with:
- It should not longer panic when rendered on small areas.
Row
s are now a collection ofCell
s, themselves wrapping aText
. This means you can style the entireTable
, an entireRow
, an entireCell
and rely on the styling capabilities ofText
to get full control over the look of yourTable
.Row
s can have multiple lines.- The header is now optional and is just another
Row
always visible at the top. Row
s can have a bottom margin.- The header alignment is no longer off when an item is selected.
Taking the example of the code in examples/demo/ui.rs
, this is what you may have to change:
let failure_style = Style::default()
.fg(Color::Red)
.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" {
up_style
} else {
failure_style
};
- Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style)
+ Row::new(vec![s.name, s.location, s.status]).style(style)
});
- let table = Table::new(header.iter(), rows)
+ let table = Table::new(rows)
+ .header(
+ Row::new(vec!["Server", "Location", "Status"])
+ .style(Style::default().fg(Color::Yellow))
+ .bottom_margin(1),
+ )
.block(Block::default().title("Servers").borders(Borders::ALL))
- .header_style(Style::default().fg(Color::Yellow))
.widths(&[
Constraint::Length(15),
Constraint::Length(15),
Here, we had to:
- Change the way we construct
Row
which is no longer anenum
but astruct
. It accepts anything that can be converted to an iterator of things that can be converted to aCell
- The header is no longer a required parameter so we use
Table::header
to set it.Table::header_style
has been removed since the style can be directly set usingRow::style
. In addition, we want to preserve the old margin between the header and the rest of the rows so we add a bottom margin to the header usingRow::bottom_margin
.
You may want to look at the documentation of the different types to get a better understanding:
Fixes
- Fix handling of Non Breaking Space (NBSP) in wrapped text in
Paragraph
widget.
Features
- Add
Style::reset
to create aStyle
resetting all styling properties when applied. - Add an option to render the
Gauge
widget with unicode blocks. - Manage common project tasks with
cargo-make
rather thanmake
for easier on-boarding.
v0.13.0 - 2020-11-14
Features
- Add
LineGauge
widget which is a more compact variant of the existingGauge
. - Bump
crossterm
to 0.18
Fixes
- Take into account the borders of the
Table
widget when the widths of columns is controlled byPercentage
andRatio
constraints.
v0.12.0 - 2020-09-27
Features
- Make it easier to work with string with multiple lines in
Text
(#361).
Fixes
- Fix a style leak in
Graph
so components drawn on top of the plotted data (i.e legend and axis titles) are not affected by the style of theDataset
s (#388). - Make sure
BarChart
shows bars with the max height only when the plotted data is actually equal to the max (#383).
v0.11.0 - 2020-09-20
Features
- Add the dot character as a new type of canvas marker (#350).
- Support more style modifiers on Windows (#368).
Fixes
- Clearing the terminal through
Terminal::clear
will cause the whole UI to be redrawn (#380). - Fix incorrect output when the first diff to draw is on the second cell of the terminal (#347).
v0.10.0 - 2020-07-17
Breaking changes
Easier cursor management
A new method has been added to Frame
called set_cursor
. It lets you specify where the cursor
should be placed after the draw call. Furthermore like any other widgets, if you do not set a cursor
position during a draw call, the cursor is automatically hidden.
For example:
fn draw_input(f: &mut Frame, app: &App) {
if app.editing {
let input_width = app.input.width() as u16;
// The cursor will be placed just after the last character of the input
f.set_cursor((input_width + 1, 0));
} else {
// We are no longer editing, the cursor does not have to be shown, set_cursor is not called and
// thus automatically hidden.
}
}
In order to make this possible, the draw closure takes in input &mut Frame
instead of mut Frame
.
Advanced text styling
It has been reported several times that the text styling capabilities were somewhat limited in many places of the crate. To solve the issue, this release includes a new set of text primitives that are now used by a majority of widgets to provide flexible text styling.
Text
is replaced by the following types:
Span
: a string with a unique style.Spans
: a string with multiple styles.Text
: a multi-lines string with multiple styles.
However, you do not always need this complexity so the crate provides From
implementations to
let you use simple strings as a default and switch to the previous primitives when you need
additional styling capabilities.
For example, the title of a Block
can be set in the following ways:
// A title with no styling
Block::default().title("My title");
// A yellow title
Block::default().title(Span::styled("My title", Style::default().fg(Color::Yellow)));
// A title where "My" is bold and "title" is a simple string
Block::default().title(vec![
Span::styled("My", Style::default().add_modifier(Modifier::BOLD)),
Span::from("title")
]);
Buffer::set_spans
andBuffer::set_span
were added.Paragraph::new
expects an input that can be converted to aText
.Block::title_style
is deprecated.Block::title
expects aSpans
.Tabs
expects a list ofSpans
.Gauge
custom label is now aSpan
.Axis
title and labels areSpans
(as a consequenceChart
no longer has generic bounds).
Incremental styling
Previously Style
was used to represent an exhaustive set of style rules to be applied to an UI
element. It implied that whenever you wanted to change even only one property you had to provide the
complete style. For example, if you had a Block
where you wanted to have a green background and
a title in bold, you had to do the following:
let style = Style::default().bg(Color::Green);
Block::default()
.style(style)
.title("My title")
// Here we reused the style otherwise the background color would have been reset
.title_style(style.modifier(Modifier::BOLD));
In this new release, you may now write this as:
Block::default()
.style(Style::default().bg(Color::Green))
// The style is not overidden anymore, we simply add new style rule for the title.
.title(Span::styled("My title", Style::default().add_modifier(Modifier::BOLD)))
In addition, the crate now provides a method patch
to combine two styles into a new set of style
rules:
let style = Style::default().modifer(Modifier::BOLD);
let style = style.patch(Style::default().add_modifier(Modifier::ITALIC));
// style.modifer == Modifier::BOLD | Modifier::ITALIC, the modifier has been enriched not overidden
Style::modifier
has been removed in favor ofStyle::add_modifier
andStyle::remove_modifier
.Buffer::set_style
has been added.Buffer::set_background
is deprecated.BarChart::style
no longer set the style of the bars. UseBarChart::bar_style
in replacement.Gauge::style
no longer set the style of the gauge. UseGauge::gauge_style
in replacement.
List with item on multiple lines
The List
widget has been refactored once again to support items with variable heights and complex
styling.
List::new
expects an input that can be converted to aVec<ListItem>
whereListItem
is a wrapper around the item content to provide additional styling capabilities.ListItem
contains aText
.List::items
has been removed.
// Before
let items = vec![
"Item1",
"Item2",
"Item3"
];
List::default().items(items.iters());
// After
let items = vec![
ListItem::new("Item1"),
ListItem::new("Item2"),
ListItem::new("Item3"),
];
List::new(items);
See the examples for more advanced usages.
More wrapping options
Paragraph::wrap
expects Wrap
instead of bool
to let users decided whether they want to trim
whitespaces when the text is wrapped.
// before
Paragraph::new(text).wrap(true)
// after
Paragraph::new(text).wrap(Wrap { trim: true }) // to have the same behavior
Paragraph::new(text).wrap(Wrap { trim: false }) // to use the new behavior
Horizontal scrolling in paragraph
You can now scroll horizontally in Paragraph
. The argument of Paragraph::scroll
has thus be
changed from u16
to (u16, u16)
.
Features
Serialization of style
You can now serialize and de-serialize Style
using the optional serde
feature.
v0.9.5 - 2020-05-21
Bug Fixes
- Fix out of bounds panic in
widgets::Tabs
when the widget is rendered on small areas.
v0.9.4 - 2020-05-12
Bug Fixes
- Ignore zero-width graphemes in
Buffer::set_stringn
.
v0.9.3 - 2020-05-11
Bug Fixes
- Fix usize overflows in
widgets::Chart
when a dataset is empty.
v0.9.2 - 2020-05-10
Bug Fixes
- Fix usize overflows in
widgets::canvas::Line
drawing algorithm.
v0.9.1 - 2020-04-16
Bug Fixes
- The
List
widget now takes into account the width of thehighlight_symbol
when calculating the total width of its items. It prevents items to overflow outside of the widget area.
v0.9.0 - 2020-04-14
Features
- Introduce stateful widgets, i.e widgets that can take advantage of keeping some state around between two draw calls (#210 goes a bit more into the details).
- Allow a
Table
row to be selected.
// State initialization
let mut state = TableState::default();
// In the terminal.draw closure
let header = ["Col1", "Col2", "Col"];
let rows = [
Row::Data(["Row11", "Row12", "Row13"].into_iter())
];
let table = Table::new(header.into_iter(), rows.into_iter());
f.render_stateful_widget(table, area, &mut state);
// In response to some event:
state.select(Some(1));
- Add a way to choose the type of border used to draw a block. You can now choose from plain, rounded, double and thick lines.
- Add a
graph_type
property on theDataset
of aChart
widget. By default it will beScatter
where the points are drawn as is. An other option isLine
where a line will be draw between each consecutive points of the dataset. - Style methods are now const, allowing you to initialize const
Style
objects. - Improve control over whether the legend in the
Chart
widget is shown or not. You can now set custom constraints usingChart::hidden_legend_constraints
. - Add
Table::header_gap
to add some space between the header and the first row. - Remove
log
from the dependencies - Add a way to use a restricted set of unicode symbols in several widgets to
improve portability in exchange of a degraded output. (see
BarChart::bar_set
,Sparkline::bar_set
andCanvas::marker
). You can check how the--enhanced-graphics
flag is used in the demos.
Breaking Changes
Widget::render
has been deleted. You should now useFrame::render_widget
to render a widget on the correspondingFrame
. This makes theWidget
implementation totally decoupled from theFrame
.
// Before
Block::default().render(&mut f, size);
// After
let block = Block::default();
f.render_widget(block, size);
Widget::draw
has been renamed toWidget::render
and the signature has been updated to reflect that widgets are consumable objects. Thus the method takesself
instead of&mut self
.
// Before
impl Widget for MyWidget {
fn draw(&mut self, area: Rect, buf: &mut Buffer) {
}
}
/// After
impl Widget for MyWidget {
fn render(self, arera: Rect, buf: &mut Buffer) {
}
}
Widget::background
has been replaced byBuffer::set_background
// Before
impl Widget for MyWidget {
fn render(self, arera: Rect, buf: &mut Buffer) {
self.background(area, buf, self.style.bg);
}
}
// After
impl Widget for MyWidget {
fn render(self, arera: Rect, buf: &mut Buffer) {
buf.set_background(area, self.style.bg);
}
}
- Update the
Shape
trait for objects that can be draw on aCanvas
widgets. Instead of returning an iterator over its points, aShape
is given aPainter
object that provides apaint
as well as aget_point
method. This gives theShape
more information about the surface it will be drawn to. In particular, this change allows theLine
shape to use a more precise and efficient drawing algorithm (Bresenham's line algorithm). SelectableList
has been deleted. You can now take advantage of the associatedListState
of theList
widget to select an item.
// Before
List::new(&["Item1", "Item2", "Item3"])
.select(Some(1))
.render(&mut f, area);
// After
// State initialization
let mut state = ListState::default();
// In the terminal.draw closure
let list = List::new(&["Item1", "Item2", "Item3"]);
f.render_stateful_widget(list, area, &mut state);
// In response to some events
state.select(Some(1));
widgets::Marker
has been moved tosymbols::Marker
v0.8.0 - 2019-12-15
Breaking Changes
- Bump crossterm to 0.14.
- Add cross symbol to the symbols list.
Bug Fixes
- Use the value of
title_style
to style the title ofAxis
.
v0.7.0 - 2019-11-29
Breaking Changes
- Use
Constraint
instead of integers to specify the widths of theTable
widget's columns. This will allow more responsive tables.
Table::new(header, row)
.widths(&[15, 15, 10])
.render(f, chunk);
becomes:
Table::new(header, row)
.widths(&[
Constraint::Length(15),
Constraint::Length(15),
Constraint::Length(10),
])
.render(f, chunk);
- Bump crossterm to 0.13.
- Use Github Actions for CI (Travis and Azure Pipelines integrations have been deleted).
Features
- Add support for horizontal and vertical margins in
Layout
.
v0.6.2 - 2019-07-16
Features
Text
implements PartialEq
Bug Fixes
- Avoid overflow errors in canvas
v0.6.1 - 2019-06-16
Bug Fixes
- Avoid a division by zero when all values in a barchart are equal to 0.
- Fix the inverted cursor position in the curses backend.
- Ensure that the correct terminal size is returned when using the crossterm backend.
- Avoid highlighting the separator after the selected item in the Tabs widget.
v0.6.0 - 2019-05-18
Breaking Changes
- Update crossterm backend
v0.5.1 - 2019-04-14
Bug Fixes
- Fix a panic in the Sparkline widget
v0.5.0 - 2019-03-10
Features
- Add a new curses backend (with Windows support thanks to
pancurses
). - Add
Backend::get_cursor
andBackend::set_cursor
methods to query and set the position of the cursor. - Add more constructors to the
Crossterm
backend. - Add a demo for all backends using a shared UI and application state.
- Add
Ratio
as a new variant of layoutConstraint
. It can be used to define exact ratios constraints.
Breaking Changes
- Add support for multiple modifiers on the same
Style
by changingModifier
from an enum to a bitflags struct.
So instead of writing:
let style = Style::default().add_modifier(Modifier::Italic);
one should use:
let style = Style::default().add_modifier(Modifier::ITALIC);
// or
let style = Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD);
Bug Fixes
- Ensure correct behavoir of the alternate screens with the
Crossterm
backend. - Fix out of bounds panic when two
Buffer
are merged.
v0.4.0 - 2019-02-03
Features
- Add a new canvas shape:
Rectangle
. - Official support of
Crossterm
backend. - Make it possible to choose the divider between
Tabs
. - Add word wrapping on Paragraph.
- The gauge widget accepts a ratio (f64 between 0 and 1) in addition of a percentage.
Breaking Changes
- Upgrade to Rust 2018 edition.
Bug Fixes
- Fix rendering of double-width characters.
- Fix race condition on the size of the terminal and expose a size that is
safe to use when drawing through
Frame::size
. - Prevent unsigned int overflow on large screens.
v0.3.0 - 2018-11-04
Features
- Add experimental test backend
v0.3.0-beta.3 - 2018-09-24
Features
show_cursor
is called whenTerminal
is dropped if the cursor is hidden.
v0.3.0-beta.2 - 2018-09-23
Breaking Changes
- Remove custom
termion
backends. This is motivated by the fact thattermion
structs are meant to be combined/wrapped to provide additional functionalities to the terminal (e.g AlternateScreen, Mouse support, ...). Thus providing exclusive types do not make a lot of sense and give a false hint that additional features cannot be used together. The recommended approach is now to create your own version ofstdout
:
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
and then to create the corresponding termion
backend:
let backend = TermionBackend::new(stdout);
The resulting code is more verbose but it works with all combinations of
additional termion
features.
v0.3.0-beta.1 - 2018-09-08
Breaking Changes
- Replace
Item
by a generic and flexibleText
that can be used in bothParagraph
andList
widgets. - Remove unecessary borrows on
Style
.
v0.3.0-beta.0 - 2018-09-04
Features
- Add a basic
Crossterm
backend
Breaking Changes
- Remove
Group
and introduceLayout
in its placeTerminal
is no longer required to compute a layoutSize
has been renamedConstraint
- Widgets are rendered on a
Frame
instead of aTerminal
in order to avoid mixingdraw
andrender
calls draw
onTerminal
expects a closure where the UI is built by rendering widgets on the givenFrame
- Update
Widget
traitdraw
takes area by valuerender
takes aFrame
instead of aTerminal
- All widgets use the consumable builder pattern
SelectableList
can have no selected item and the highlight symbol is hidden in this case- Remove markup langage inside
Paragraph
.Paragraph
now expects an iterator ofText
items
v0.2.3 - 2018-06-09
Features
- Add
start_corner
option forList
- Add more text aligment options for
Paragraph
v0.2.2 - 2018-05-06
Features
Terminal
implementsDebug
Breaking Changes
- Use
FnOnce
instead ofFnMut
in Group::render
v0.2.1 - 2018-04-01
Features
- Add
AlternateScreenBackend
intermion
backend - Add
TermionBackend::with_stdout
in order to let an user of the library provides its own termion struct - Add tests and documentation for
Buffer::pos_of
- Remove leading whitespaces when wrapping text
Bug Fixes
- Fix
debug_assert
inBuffer::pos_of
- Pass the style of
SelectableList
to the underlyingList
- Fix missing character when wrapping text
- Fix panic when specifying layout constraints
v0.2.0 - 2017-12-26
Features
- Add
MouseBackend
intermion
backend to handle scroll and mouse events - Add generic
Item
for items in aList
- Drop
log4rs
as a dev-dependencies in favor ofstderrlog
Breaking Changes
- Rename
TermionBackend
toRawBackend
(to distinguish it from theMouseBackend
) - Generic parameters for
List
to allow passing iterators as items - Generic parameters for
Table
to allow using iterators as rows and header - Generic parameters for
Tabs
- Rename
border
bitflags toBorders