Build terminal user interfaces and dashboards using Rust

Related tags

rust terminal dashboard tui
Overview

tui-rs

Build Status Crate Status Docs Status

Demo cast under Linux Termite with Inconsolata font 12pt

tui-rs is a Rust library to build rich terminal user interfaces and dashboards. It is heavily inspired by the Javascript library blessed-contrib and the Go library termui.

The library itself supports four different backends to draw to the terminal. You can either choose from:

However, some features may only be available in one of the four.

The library is based on the principle of immediate rendering with intermediate buffers. This means that at each new frame you should build all widgets that are supposed to be part of the UI. While providing a great flexibility for rich and interactive UI, this may introduce overhead for highly dynamic content. So, the implementation try to minimize the number of ansi escapes sequences generated to draw the updated UI. In practice, given the speed of Rust the overhead rather comes from the terminal emulator than the library itself.

Moreover, the library does not provide any input handling nor any event system and you may rely on the previously cited libraries to achieve such features.

Rust version requirements

Since version 0.10.0, tui requires rustc version 1.44.0 or greater.

Documentation

Demo

The demo shown in the gif can be run with all available backends (examples/*_demo.rs files). For example to see the termion version one could run:

cargo run --example termion_demo --release -- --tick-rate 200

where tick-rate is the UI refresh rate in ms.

The UI code is in examples/demo/ui.rs while the application state is in examples/demo/app.rs.

Beware that the termion_demo only works on Unix platforms. If you are a Windows user, you can see the same demo using the crossterm backend with the following command:

cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200

If the user interface contains glyphs that are not displayed correctly by your terminal, you may want to run the demo without those symbols:

cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200 --enhanced-graphics false

Widgets

The library comes with the following list of widgets:

Click on each item to see the source of the example. Run the examples with with cargo (e.g. to run the demo cargo run --example demo), and quit by pressing q.

You can run all examples by running make run-examples.

Third-party widgets

Apps using tui

Alternatives

You might want to checkout Cursive for an alternative solution to build text user interfaces in Rust.

License

MIT

Issues
  • Text Style Background should affect whole Row/Cell in List and Table

    Text Style Background should affect whole Row/Cell in List and Table

    When using Text::styled in a List or Table, the background color only affects the text. I believe it should affect the whole Row/Cell in which the text is in.

    To Reproduce

    let splits_list = splits.iter().map(|(txt)| {
        Text::styled(Cow::from(txt), 
            Style::default()
                .bg(Color::Red))
    });
    let mut splits_list = List::new(splits_list)
        .style(normal_style1);
    f.render(&mut splits_list, chunks[0]);
    

    Screenshots

    Tui List BG

    Desktop:

    • OS: Tested on [Linux Mint Cinnamon and Windows 10]
    • Terminal Emulator [Gnome Terminal, Power Shell and Cmd]
    • Font [Monospace and Consolas]
    • Crate version [0.8]
    • Backend [crossterm]
    bug 
    opened by Ynscription 14
  • Control cursor position and visibility via Frame

    Control cursor position and visibility via Frame

    This implements the idea I outlined in https://github.com/fdehau/tui-rs/issues/91#issuecomment-634998916:

    can't Frame have a field like Option<(u16, u16)>, which is the cursor's desired position after drawing the frame? None means the cursor is hidden, Some with a position means the cursor is shown and is positioned at the given cell.

    The implementation deviates from the idea in two ways.

    First of all, Frame is moved into the drawing closure — there is no way for Terminal::draw() to examine frame's fields afterwards. This could be resolved in two ways:

    1. make Terminal::draw() require FnOnce(&mut Frame<B>). This is very clean, but it's a breaking change, so I decided against that.

    2. store that Option<(u16, u16)> in the Terminal itself, and make Frame just pass the calls through. That's a bit of a hack, because I don't think it's Terminal's responsibility to store this data; it's Frame's job to describe what has to be drawn and how.

    The second option is a non-breaking change, so I went with that.

    That decision led to the second deviation. Terminal already has an API for controlling the cursor: hide_cursor(), show_cursor(), set_cursor(). These calls have immediate effect on the terminal (modulo stdout buffering). This conflicts with the proposed API, and could be resolved in two ways:

    1. make the old API non-public. This doesn't seem right to me, since Terminal is supposed to be a low-level wrapper around the backend.

    2. make the new API opt-in. Up until the first call to Frame::set_desired_cursor_position(), calls to draw() don't affect cursor visibility, so the user is free to use Terminal::hide_cursor() and Terminal::show_cursor(). After the first call to the new API, however, the cursor position and visibility is always enforced after each Terminal::draw() call.

    The first option is not viable, so I went with the second one.

    I think that in the long term, tui-rs should change the signature of Terminal::draw(), to move all of this to Frame, and make Terminal a completely low-level thing again. If that's the plan, it might make sense to make the new API on Terminal non-public now, so people don't depend on what is going to be removed. @fdehau, I'm also okay with re-doing the PR to change the signature now, if you're ok with a breaking change.

    opened by Minoru 14
  • Preserve Indentation of Unwrapped Lines

    Preserve Indentation of Unwrapped Lines

    Thanks for the wonderful library! :heart:

    Issue

    I ran into some unexpected behaviour recently when I was attempting to print a paragraph with some indentation in wrapping mode – TUI was stripping the leading whitespace from my lines.

    I've since discovered that this is an intentional behaviour, but that leaves me unable to print a paragraph like this:

    MW Word of the Day                                                            
    *verb*                                                                        
       1) to diffuse through or penetrate something                               
       2) to spread or diffuse through                                            
       3) to pass through the pores or interstices of
    

    Changes

    In this PR, I simply disabled this behaviour, as I feel it's more predicable.

    The above paragraph now wraps as follows:

    MW Word of the Day            
    *verb*                        
       1) to diffuse through or   
    penetrate something           
       2) to spread or diffuse    
    through                       
       3) to pass through the     
    pores or interstices of
    

    Justification

    Slightly funky? Perhaps, but much more predicable and arguably clearer than no indentation whatsoever.

    Comparing to the current master TUI branch:

    MW Word of the Day            
    *verb*                        
    1) to diffuse through or      
    penetrate something           
    2) to spread or diffuse       
    through                       
    3) to pass through the pores  
    or interstices of 
    

    The lack of indentation makes it hard to tell that those are distinct "bullet points". The user can always trim strings in their application, but can't re-add the spacing TUI strips.

    Tests

    I adapted one of the leading-space tests to test indentation is preserved, but the other two I removed. If you are curious, the output of the removed tests was:

    ---- widgets::reflow::test::line_composer_char_plus_lots_of_spaces stdout ----
    thread 'widgets::reflow::test::line_composer_char_plus_lots_of_spaces' panicked at 'assertion failed: `(left == right)`
      left: `["a", "                    ", "                    ", "       "]`,
     right: `["a", ""]`', src/widgets/reflow.rs:401:9
    
    ---- widgets::reflow::test::line_composer_lots_of_spaces stdout ----
    thread 'widgets::reflow::test::line_composer_lots_of_spaces' panicked at 'assertion failed: `(left == right)`
      left: `["                    ", "                    ", "                    ", "      "]`,
     right: `[""]`', src/widgets/reflow.rs:385:9
    

    Are a load of lines filled by nothing but spaces useful? Probably not, but at least this gives the user the choice to keep or remove them. This new behaviour is also somewhat more in line with the LineTruncator which leaves the trailing whitespace.

    opened by TheLostLambda 12
  • Implement style merging for highlight styles

    Implement style merging for highlight styles

    Previously the highlight style (even if not present) overrode the row styles for a table and list:

    image

    With this change, if there is no highlight style defined, the selected item follows the style of the row:

    image

    When a highlight style is defined, it is merged with the data style:

    image


    This is now implemented on top of StyleDiff. The highlight_style and header_style are accepted through that. The basic style is still a normal Style as this should be the lowest priority style so there's nothing to patch that on top of.

    I also wish I could have added DiffStyled variants to Text and Row, but that would have been a compatibility break. Maybe it should be considered for the next semver-major release?

    There's also some overall improvements here in how styles get applied between highlight symbols, etc.

    opened by Rantanen 10
  • crossterm (re)size broken

    crossterm (re)size broken

    Describe the bug In order to prepare the pull request for the Clear-feature (https://github.com/fdehau/tui-rs/pull/244) I pulled in the master and realized that crossterm terminal sizing seems broken: (see screenshot)

    The terminal seems to have a default size and not using up the actual terminal size. Also the resize events are not changing anything.

    To Reproduce In my case (macOS) I only need to run the Crossterm-demo:

    cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- -tick-rate 200
    

    Expected behavior A clear and concise description of what you expected to happen.

    Screenshots image

    Desktop (please complete the following information):

    • OS: macos
    • Terminal Emulator: iTerm2
    • Crate version: master
    • Backend: crossterm

    Additional context This was working as intended in the previous release

    bug 
    opened by extrawurst 10
  • Intersted in crossterm implementation?

    Intersted in crossterm implementation?

    Hi, I am the writer of Crossterm cross-platform terminal manipulation library. In a short time, I will launch a new version of my crate which has than support for user input. It will then have all the features that termion has but then crossplatform.

    Interested? is there a place where I could contact you like discord or so?

    enhancement 
    opened by TimonPost 10
  • Crossterm+Windows support

    Crossterm+Windows support

    Would love to try using tui, but when I run the demo it looks like it's not quite ready to use in Windows.

    Here's running the crossterm_demo in Windows:

    image

    Is there a better way to get Windows support aside from crossterm? If not, are there any plans for making Windows supported?

    enhancement 
    opened by jonathandturner 9
  • Crossterm backend input is not working

    Crossterm backend input is not working

    I noticed, and I hear from some other people that the input of the crossterm backend is not working correctly, especially talking about the crossterm demo example. The keys seem not to be recorded. I can have a look at this in a few days, but if there are people that can do that as well it will be appreciated.

    opened by TimonPost 9
  • Switch for rounded corners on widgets

    Switch for rounded corners on widgets

    I would like the option of building widgets with rounded corners given the unicode characters for this exist. I have tried to figure out how to implement that option but am having a difficult time figuring out the flow that leads to building a widget and where that option would be introduced. The below snippets may be confusing, so you can skip those and check https://github.com/stevensonmt/tui-rs/commit/db80ae911174cc388c10092e12a2713d7c8b24ab if that is easier.

    In symbols::line I have this:

    pub mod line {
        pub const TOP_RIGHT: &str = "┐";
        pub const TOP_RIGHT_ROUNDED: &str = "╮";
        pub const VERTICAL: &str = "│";
        pub const HORIZONTAL: &str = "─";
        pub const TOP_LEFT: &str = "┌";
        pub const TOP_LEFT_ROUNDED: &str = "╭";
        pub const BOTTOM_RIGHT: &str = "┘";
        pub const BOTTOM_RIGHT_ROUNDED: &str = "╯";
        pub const BOTTOM_LEFT: &str = "└";
        pub const BOTTOM_LEFT_ROUNDED: &str = "╰";
        pub const VERTICAL_LEFT: &str = "┤";
        pub const VERTICAL_RIGHT: &str = "├";
        pub const HORIZONTAL_DOWN: &str = "┬";
        pub const HORIZONTAL_UP: &str = "┴";
    }
    

    and for layout::Rect

    pub struct Rect {
        pub x: u16,
        pub y: u16,
        pub width: u16,
        pub height: u16,
        pub rounded_corners: bool,
    }
    
    impl Default for Rect {
        fn default() -> Rect {
            Rect {
                x: 0,
                y: 0,
                width: 0,
                height: 0,
                rounded_corners: false,
            }
        }
    

    and in widgets::block:

            // Corners
            if self.borders.contains(Borders::LEFT | Borders::TOP) {
                let top_left = match area.rounded_corners {
                    true => line::TOP_LEFT_ROUNDED,
                    _ => line::TOP_LEFT,
                };
                buf.get_mut(area.left(), area.top())
                    .set_symbol(top_left)
                    .set_style(self.border_style);
            }
            if self.borders.contains(Borders::RIGHT | Borders::TOP) {
                let top_right = match area.rounded_corners {
                    true => line::TOP_RIGHT_ROUNDED,
                    _ => line::TOP_RIGHT,
                };
                buf.get_mut(area.right() - 1, area.top())
                    .set_symbol(top_right)
                    .set_style(self.border_style);
            }
            if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
                let bottom_left = match area.rounded_corners {
                    true => line::BOTTOM_LEFT_ROUNDED,
                    _ => line::BOTTOM_LEFT,
                };
                buf.get_mut(area.left(), area.bottom() - 1)
                    .set_symbol(bottom_left)
                    .set_style(self.border_style);
            }
            if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
                let bottom_right = match area.rounded_corners {
                    true => line::BOTTOM_RIGHT_ROUNDED,
                    _ => line::BOTTOM_RIGHT,
                };
                buf.get_mut(area.right() - 1, area.bottom() - 1)
                    .set_symbol(bottom_right)
                    .set_style(self.border_style);
            }
    

    As I've dug into the rabbit hole of the build pattern I find that terminal_size() returns a rectangle (which seems a bit counter-intuitive to me as "size" implies just dimensions not shape). This seems to be the starting point so I've added a rounded_corners: bool field to the termion::TermionBackend struct and changed the size() method to:

        /// Return the size of the terminal
        fn size(&self) -> io::Result<Rect> {
            let terminal = termion::terminal_size()?;
            Ok(Rect::new(
                0,
                0,
                terminal.0,
                terminal.1,
                self.rounded_corners,
            ))
        }
    

    By then adding appropriate boolean parameters to the termion_demo.rs and ui.rs files I have been able to compile the demo but the corners are still sharp not rounded. Any suggestions?

    opened by stevensonmt 9
  • Version 0.10: Spans can't contain line-breaks

    Version 0.10: Spans can't contain line-breaks

    Describe the bug I understand that multiple lines in a paragraph are intended to be represented using Text, but I seem unable to apply a span over several lines which I think is still very useful.

    To Reproduce Using the current development version from Git, I'm ultimately building the following:

    [src/console/render.rs:58] vec![Spans :: from(text), Spans :: from("This is on the second line...")] = [
        Spans(
            [
                Span {
                    content: "To Kill a Mockingbird\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: ITALIC,
                        sub_modifier: (empty),
                    },
                },
                Span {
                    content: "*noun*\n   1) outfit, equipment, attire\n\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: BOLD,
                        sub_modifier: (empty),
                    },
                },
                Span {
                    content: "1) obstreperous\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: (empty),
                        sub_modifier: (empty),
                    },
                },
                Span {
                    content: "2) invective\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: (empty),
                        sub_modifier: (empty),
                    },
                },
                Span {
                    content: "3) habiliment\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: (empty),
                        sub_modifier: (empty),
                    },
                },
                Span {
                    content: "4) furore\n",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: (empty),
                        sub_modifier: (empty),
                    },
                },
            ],
        ),
        Spans(
            [
                Span {
                    content: "This is on the second line...",
                    style: Style {
                        fg: None,
                        bg: None,
                        add_modifier: (empty),
                        sub_modifier: (empty),
                    },
                },
            ],
        ),
    ]
    
    

    Which results in the following (note that all of my \n's were ignored):

    To Kill a Mockingbird*noun*   1) outfit, equipment, attire1) obstreperous2)   
    invective3) habiliment4) furore                                               
    This is on the second line...      
    

    Expected behavior

    I'm really looking for what I had before the text refactoring:

    To Kill a Mockingbird
    *noun*
       1) outfit, equipment, attire
    
    1) obstreperous
    2) invective
    3) habiliment
    4) furore                                               
    This is on the second line...      
    

    Desktop:

    • OS: Linux
    • Terminal Emulator: gnome-terminal
    • Font: IBM Plex Mono
    • Crate version:
    name = "tui"
    version = "0.9.5"
    source = "git+https://github.com/fdehau/tui-rs.git#6b52c91257f9073e4fe913f15bee0716b45dfd0d"
    
    • Backend: crossterm
    bug 
    opened by TheLostLambda 8
  • Key::Char('q') seems to be kinda hardwired to

    Key::Char('q') seems to be kinda hardwired to "close app".

    Description

    The key q is often used to either break; or do a self.should_quit = true; Which is nice. Makes sense. But when not using it: a q key press freezes the app.

    To Reproduce

    Using the termion_demo. Minimum code change and example.

    1. in demo/app.rs change pub fn starting on line #202 to:
        pub fn on_key(&mut self, c: char) {
            match c {
                // 'q' => {
                //     self.should_quit = true;
                // }
                't' => {
                    self.show_chart = !self.show_chart;
                }
                _ => {}
            }
        }
    
    1. cargo run --example termion_demo
    2. Move around with arrow keys
    3. Press key q
    4. Try to move around with arrow keys again (nothing happens, the cli app is frozen)

    Expected behavior

    Nothing besides that the app should keep doings its thing.

    Environment

    • OS: Linux
    • Terminal Emulator: alacritty
    • Crate version: 0.15
    • Backend: termion
    bug 
    opened by salkin-mada 0
  • Wide labels (3 chars and more) in chart axis lead to panic with 'attempt to subtract with overflow'

    Wide labels (3 chars and more) in chart axis lead to panic with 'attempt to subtract with overflow'

    Description

    When adding lables with more than 2 characters to chart axis, the f.render_widget(chart, area) call panics saying that there is an overflow in tui-0.15.0/src/widgets/chart.rs:400:25

    To Reproduce

    https://gist.github.com/samoylovfp/97d756e3c62a8bff9862e2012324faf2 Changing label on line 16 to have less than 3 symbols makes it work

    Expected behavior

    Expected to not panic

    Environment

    • OS: Linux
    • Terminal Emulator: wezterm, gnome-terminal
    • Crate version: 0.15
    • Backend: termion
    bug 
    opened by samoylovfp 0
  • Fix minor grammatical errors in docs

    Fix minor grammatical errors in docs

    Description

    A missing "and" after "an" (which I do all the time) and some tense clarification for some of the docs

    Testing guidelines

    no testing should be needed

    Checklist

    • [x] I have read the contributing guidelines.
    • [x] I have added relevant tests.
    • [x] I have documented all new additions.
    opened by jmrgibson 0
  • Using LineGauge in Table/Cell - rendering a table-like thread-overview with multiple gauges

    Using LineGauge in Table/Cell - rendering a table-like thread-overview with multiple gauges

    Hello, I attempt to render a thread progress overview using TUI. The progress itself is provided as a reference to a vector of AtomicUints wrapped in Arcs. My idea was to render this using a table, as I had assumed given the layout constraints this would be the easiest way to do this. See the example below:

    | #ID | Progress | Status | | ------------- | ------------- | ------------- | | 0| [-------             ] | 30% | | 1| [---------          ] | 34% | | 2| [---                   ] | 13% |

    The real table is longer and exact size is not known at compile-time, but dependent on the length of the vector. Now, I've tried to adapt the crossterm_demo like this:

    let chunks = Layout::default()
        .constraints([Constraint::Percentage(100)].as_ref())
        .margin(1)
        .split(area);
    let block = Block::default().borders(Borders::ALL).title("Graphs");
    f.render_widget(block, area);
    
    let i = 0;
    let rows = states.iter().map(|item| {
        let thread_id_cell = Cell::from(format!("{:2}", &i));
        let progress = item.load(Ordering::Relaxed);
        let ratio: f64 = f64::from(progress as u32) / 100.00;
        let line_gauge_cell = LineGauge::default()
            .gauge_style(Style::default().fg(Color::Magenta))
            .line_set(symbols::line::THICK)
            .ratio(ratio);
        let progress_cell = Cell::from(format!("{:.2}%", &progress));
        let cells = vec![thread_id_cell, Cell::from(line_gauge_cell), progress_cell];
        Row::new(cells).height(1).bottom_margin(1)
    });
    
    let t = Table::new(rows)
        .block(Block::default().borders(Borders::ALL).title("Table"))
        .highlight_symbol(">> ")
        .widths(&[
            Constraint::Max(5),
            Constraint::Percentage(90),
            Constraint::Max(5),
        ]);
    
    f.render_widget(t, chunks[0]);
    

    But I get an error when attempting to create a Cell from the LineGauge because the trait "From<LineGauge<'_>>" is not implemented for "tui::text::Text<'_>". Considering other widgets available, I wasn't sure what would be the best way to go here, as the table layout seems hard to reproduce.

    Am I missing conversion routines or the like that would allow using the LineGauge within Cell? If not, is there a table-like layout available that doesn't require specifying extensive layout constraints?

    EDIT: this shouldn't have had the bug label, but I'm unfortunately unable to change it.

    bug 
    opened by BitVortex 0
  • Fixed typos in comments.

    Fixed typos in comments.

    opened by mschulteg 0
  • docs: add gpg-tui to the

    docs: add gpg-tui to the "apps using tui" list

    opened by orhun 0
  • border! macro for the easy and pretty creation of Borders bitflags.

    border! macro for the easy and pretty creation of Borders bitflags.

    Description

    I have added the border! macro. It can be called with any combination of TOP, BOTTOM, LEFT, RIGHT, NONE, or ALL to return a widgets::Borders object with the corrosponding flags set. This is helpful because the current methods of creating custom Borders is ugly and long. Further things such as Borders.add() or Borders.remove() do not allowing chaining which makes constructing the border you want either a multiline operation or requires oring Borders objects. This macro is cleaner, shorter, and much more readable. Compare: This is a use of Borders from the widgets::Block documentation Block::default() .title("Block") .borders(Borders::LEFT | Borders::RIGHT) .border_style(Style::default().fg(Color::White)) .border_type(BorderType::Rounded) .style(Style::default().bg(Color::Black));

    This is the same code with the border! macro Block::default() .title("Block") .borders(border!(LEFT, RIGHT)) .border_style(Style::default().fg(Color::White)) .border_type(BorderType::Rounded) .style(Style::default().bg(Color::Black));

    As you can see the second is much easier to read and understand. This is a fairly trivial example but if you need a border with three sides the differences becomes much clearer. border!(TOP, LEFT, RIGHT) instead of Borders::TOP | Borders::LEFT | Borders::RIGHT.

    The macro also provides a syntactic sugar for Borders::NONE. Instead of calling border!(NONE) or using Borders::NONE directly you can just call border!() which return Borders::NONE.

    Interally the macro functions by calling Borders::empty() and then using .insert() to insert each specified flag. If no flags are specified the macro directly returns Borders::NONE and doesn't waste effort creating an empty Borders and inserting.

    I have done my best to add inline documentation to the addition but I'm unsure if I did it correctly/formatted it correctly. it currently passes cargo test but a quick glance over it would be appreciated.

    Testing guidelines

    I have added tests for the macro to tests in border_macro.rs. These include tests for ALL, specific sides, and the syntactic sugar for NONE: border!(). All tests pass on debian linux when running cargo test.

    Checklist

    • [x] I have read the contributing guidelines.
    • [x] I have added relevant tests.
    • [x] I have documented all new additions.
    opened by aghayes 0
  • Getting Cursor Position in Wrapped Text

    Getting Cursor Position in Wrapped Text

    Problem

    I'd like to get the cursor position in a paragraph widget with wrapping enabled. This is non trivial to calculate without basically rewriting the wrapping code. More generally, I'd like to be able to get information about the way Wrap has split up my text, so that I can move the cursor throughout the text.

    Solution

    Add some function to Paragraph that allows us to get row and column information.

    enhancement 
    opened by thorlucas 0
  • Add `TableState.offset(&self) -> usize`

    Add `TableState.offset(&self) -> usize`

    Description

    Add simple getter method to TableState that returns a copy of its offset field.

    My motivating case is a program that allows scrolling a Table and an adjacent (related) Paragraph at the same time

    Testing guidelines

    I don't know that any testing is useful here since it only exposes an existing piece of data.

    Checklist

    • [x] I have read the contributing guidelines.
    • [ ] I have added relevant tests.
    • [x] I have documented all new additions.
    opened by arennow 0
  • Styling gauge label with foreground / background color has no effect

    Styling gauge label with foreground / background color has no effect

    Description

    Passing a styled span won't style fg/bg for the gauge label.

    To Reproduce

    let gauge = Gauge::default()
        .gauge_style(Style::default().fg(Color::White))
        .percent(80)
        .label(Span::styled(
            "Progress is 80",
            Style::default()
                .fg(Color::Red)
                .add_modifier(Modifier::BOLD | Modifier::ITALIC),
        ))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_type(BorderType::Plain)
                .title("Progress"),
        );
    

    Expected behavior

    For the above example, I would expect the label to be red, bold, and italic at all times. However only bold and italic get applied.

    Screenshots

    My output for reproduction example: image

    Environment

    • OS: macOS
    • Terminal Emulator: iTerm2
    • Font: JetBrainsMono (patched NerdFont)
    • Crate version: version = "0.15.0", default-features = false, features = ["crossterm"]
    • Backend: crossterm
    bug 
    opened by ecklf 0
Releases(v0.15.0)
  • v0.15.0(May 2, 2021)

    Features

    • Update crossterm to 0.19.
    • Update rand to 0.8.
    • Add a read-only view of the terminal state after the draw call (#440).

    Fixes

    • Remove compile warning in TestBackend::assert_buffer (#466).
    Source code(tar.gz)
    Source code(zip)
  • v0.14.0(Jan 1, 2021)

    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.
    • Rows are now a collection of Cells, themselves wrapping a Text. This means you can style the entire Table, an entire Row, an entire Cell and rely on the styling capabilities of Text to get full control over the look of your Table.
    • Rows can have multiple lines.
    • The header is now optional and is just another Row always visible at the top.
    • Rows 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 an enum but a struct. It accepts anything that can be converted to an iterator of things that can be converted to a Cell
    • 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 using Row::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 using Row::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 a Style 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 than make for easier on-boarding.
    Source code(tar.gz)
    Source code(zip)
  • v0.13.0(Nov 14, 2020)

    Features

    • Add LineGauge widget which is a more compact variant of the existing Gauge.
    • Bump crossterm to 0.18

    Fixes

    • Take into account the borders of the Table widget when the widths of columns is controlled by Percentage and Ratio constraints.
    Source code(tar.gz)
    Source code(zip)
  • v0.10.0(Sep 27, 2020)

    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 and Buffer::set_span were added.
    • Paragraph::new expects an input that can be converted to a Text.
    • Block::title_style is deprecated.
    • Block::title expects a Spans.
    • Tabs expects a list of Spans.
    • Gauge custom label is now a Span.
    • Axis title and labels are Spans (as a consequence Chart 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 of Style::add_modifier and Style::remove_modifier.
    • Buffer::set_style has been added. Buffer::set_background is deprecated.
    • BarChart::style no longer set the style of the bars. Use BarChart::bar_style in replacement.
    • Gauge::style no longer set the style of the gauge. Use Gauge::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 a Vec<ListItem> where ListItem is a wrapper around the item content to provide additional styling capabilities. ListItem contains a Text.
    • 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.

    Source code(tar.gz)
    Source code(zip)
  • v0.11.0(Sep 27, 2020)

    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).
    Source code(tar.gz)
    Source code(zip)
  • v0.12.0(Sep 27, 2020)

    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 the Datasets (#388).
    • Make sure BarChart shows bars with the max height only when the plotted data is actually equal to the max (#383).
    Source code(tar.gz)
    Source code(zip)
  • v0.9.0(Apr 14, 2020)

    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 the Dataset of a Chart widget. By default it will be Scatter where the points are drawn as is. An other option is Line 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 using Chart::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 and Canvas::marker). You can check how the --enhanced-graphics flag is used in the demos.

    Breaking Changes

    • Widget::render has been deleted. You should now use Frame::render_widget to render a widget on the corresponding Frame. This makes the Widget implementation totally decoupled from the Frame.
    // Before
    Block::default().render(&mut f, size);
    
    // After
    let block = Block::default();
    f.render_widget(block, size);
    
    • Widget::draw has been renamed to Widget::render and the signature has been updated to reflect that widgets are consumable objects. Thus the method takes self 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 by Buffer::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 a Canvas widgets. Instead of returning an iterator over its points, a Shape is given a Painter object that provides a paint as well as a get_point method. This gives the Shape more information about the surface it will be drawn to. In particular, this change allows the Line 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 associated ListState of the List 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 to symbols::Marker
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Apr 14, 2020)

    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 of Axis.
    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Nov 29, 2019)

    Changed

    • Use Constraint instead of integers to specify the widths of the Table 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).

    Added

    • Add support for horizontal and vertical margins in Layout.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.1(Jun 16, 2019)

    Fixed

    • 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.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(May 18, 2019)

  • v0.5.0(Mar 10, 2019)

    Added

    • Add a new curses backend (with Windows support thanks to pancurses).
    • Add Backend::get_cursor and Backend::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 layout Constraint. It can be used to define exact ratios constraints.

    Changed

    • Add support for multiple modifiers on the same Style by changing Modifier from an enum to a bitflags struct.

    So instead of writing:

    let style = Style::default().modifier(Modifier::Italic);
    

    one should use:

    let style = Style::default().modifier(Modifier::ITALIC);
    // or
    let style = Style::default().modifier(Modifier::ITALIC | Modifier::BOLD);
    

    Fixed

    • Ensure correct behavoir of the alternate screens with the Crossterm backend.
    • Fix out of bounds panic when two Buffer are merged.
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Feb 3, 2019)

    Added

    • 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.

    Changed

    • Upgrade to Rust 2018 edition.

    Fixed

    • 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.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.3(Sep 24, 2018)

  • v0.3.0-beta.2(Sep 23, 2018)

    Changed

    • Remove custom termion backends. This is motivated by the fact that termion 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 of stdout:
    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.

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.1(Sep 8, 2018)

    Changed

    • Replace Item by a generic and flexible Text that can be used in both Paragraph and List widgets.
    • Remove unecessary borrows on Style.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.0(Sep 4, 2018)

    Added

    • Add a basic Crossterm backend

    Changed

    • Remove Group and introduce Layout in its place
      • Terminal is no longer required to compute a layout
      • Size has been renamed Constraint
    • Widgets are rendered on a Frame instead of a Terminal in order to avoid mixing draw and render calls
    • draw on Terminal expects a closure where the UI is built by rendering widgets on the given Frame
    • Update Widget trait
      • draw takes area by value
      • render takes a Frame instead of a Terminal
    • 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 of Text items
    Source code(tar.gz)
    Source code(zip)
  • v0.2.3(Jun 9, 2018)

  • v0.2.2(May 6, 2018)

  • v0.2.1(Apr 1, 2018)

    Added

    • Add AlternateScreenBackend in termion 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

    Fixed

    • Fix debug_assert in Buffer::pos_of
    • Pass the style of SelectableList to the underlying List
    • Fix missing character when wrapping text
    • Fix panic when specifying layout constraints
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Dec 26, 2017)

    Added

    • Add MouseBackend in termion backend to handle scroll and mouse events
    • Add generic Item for items in a List

    Changed

    • Rename TermionBackend to RawBackend (to distinguish it from the MouseBackend)

    • 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 to Borders

    • Run latest rustfmt on all sources

    Removed

    • Drop log4rs as a dev-dependencies in favor of stderrlog
    Source code(tar.gz)
    Source code(zip)
Owner
Florian Dehau
Florian Dehau
Build terminal user interfaces and dashboards using Rust

tui-rs tui-rs is a Rust library to build rich terminal user interfaces and dashboards. It is heavily inspired by the Javascript library blessed-contri

Florian Dehau 5.2k Jun 13, 2021
Cross platform terminal library rust

Cross-platform Terminal Manipulation Library Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform te

crossterm-rs 1.2k Jun 15, 2021
A cat(1) clone with syntax highlighting and Git integration.

A cat(1) clone with syntax highlighting and Git integration. Key Features • How To Use • Installation • Customization • Project goals, alternatives [中

David Peter 27.5k Jun 10, 2021
Alacritty - A fast, cross-platform, OpenGL terminal emulator

Alacritty is a modern terminal emulator that comes with sensible defaults, but allows for extensive configuration. By integrating with other applications, rather than reimplementing their functionality, it manages to provide a flexible set of features with high performance. The supported platforms currently consist of BSD, Linux, macOS and Windows.

Alacritty 33.1k Jun 13, 2021
Cross-platform Rust library for coloring and formatting terminal output

Coloring terminal output Documentation term-painter is a cross-platform (i.e. also non-ANSI terminals) Rust library for coloring and formatting termin

Lukas Kalbertodt 67 Mar 14, 2021
A Text User Interface library for the Rust programming language

Cursive Cursive is a TUI (Text User Interface) library for rust. It uses ncurses by default, but other backends are available. It allows you to build

Alexandre Bury 2.3k Jun 11, 2021
A curated list of replacements for existing software written in Rust

Awesome Alternatives in Rust A curated list of replacements for existing software written in Rust. If you want to contribute, please read CONTRIBUTING

Takayuki Maeda 1.7k Jun 13, 2021
procs is a replacement for ps written in Rust.

procs is a replacement for ps written in Rust. Documentation quick links Features Platform Installation Usage Configuration Features Output by t

null 1.9k Jun 7, 2021
Another TUI based system monitor, this time in Rust!

Another TUI based system monitor, this time in Rust!

Caleb Bassi 2k Jun 10, 2021
A CLI utility installed as "ansi" to quickly get ANSI escape sequences. Supports the most basic ones, like colors and styles as bold or italic.

'ansi' - a CLI utility to quickly get ANSI escape codes This Rust project called ansi-escape-sequences-cli provides an executable called ansi which ca

Philipp Schuster 5 May 3, 2021
Low-level Rust library for implementing terminal command line interface, like in embedded systems.

Terminal CLI Need to build an interactive command prompt, with commands, properties and with full autocomplete? This is for you. Example, output only

HashMismatch 38 Jun 14, 2021
A Rust curses library, supports Unix platforms and Windows

pancurses pancurses is a curses library for Rust that supports both Linux and Windows by abstracting away the backend that it uses (ncurses-rs and pdc

Ilkka Halila 305 Jun 13, 2021
Readline Implementation in Rust

RustyLine Readline implementation in Rust that is based on Antirez' Linenoise Supported Platforms Unix (tested on FreeBSD, Linux and macOS) Windows cm

Katsu Kawakami 766 Jun 13, 2021
yayagram - Play nonograms/picross in your terminal

yayagram is a puzzle game in which you fill out a grid with cells based on logic and number clues.

r00ster 13 Jun 11, 2021