fdehau / tui-rs Goto Github PK
View Code? Open in Web Editor NEWBuild terminal user interfaces and dashboards using Rust
License: MIT License
Build terminal user interfaces and dashboards using Rust
License: MIT License
I noticed in the demo application the footer section does not show the last character of the line before being wrapped. I tried adding a margin
, but didn't fix it.
I don't see anything in the docs about how to control the cursor. I'd be happy to submit documentation PR, except I have no clue how to control the cursor.
I've noticed that there is no example that uses mouse to interact with the widgets? Is this possible on this library?
I would like to present stuff in a list but I'd also like stuff in it to be able to wrap long strings.
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?
AFAIK, currently there's no way to modify cursor state.. So if I want a text input, like a line editing, I'd like the user to see the cursor, so they know where they are editing. But I don't see anything like editing cursor position when editing. There are only "show" and "hide" cursor. So, how can this be achieved?
Maybe I'm just too new to rust; but it sure is damn hard with the beta of the library to break a drawing step into a separate function. Compiler puke everywhere trying to figure out the damn types. Like to pass a terminal to a function it has to be t: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>
. And I can't figure out what the return value should be on a t.draw(some closure)?;
call...
I'm making a TUI game, and I'm having trouble figuring out the best way to place a single character into a Rect
. I see the Buffer
methods and doing it that low-level is just fine, the question is how do I then render the Buffer
? Do I have to make a custom widget for that?
I'm using MacOS and on any terminal I've tried, none of the examples enter raw mode. I can still scroll around freely, my screen never locks, etc. Is this a bug??
Rustbox backend example also does not enter raw mode.
I see the style modifier Invert
but when I have a list area that can have say 10 items in it but I only have 4 items they still appear at the top and not at the bottom of the area? I'm trying to make two lists that give the data points on say a bell curve and the lists converge on the center point. If that doesn't make sense let me know and I'll try to draw something.
Just found your library and it looks awesome!
I'm aware that you wrote "the library does not provide any input handling nor any event system", but is there any example of a text input widget?
I'm trying to write a very simple chat application, so I'd just need a text input at the bottom and a scrolling text view at the top.
Two things:
It seems like a SelectableList
will always show its highlight_symbol
next to one of its items even if none of the items are selected, whereas the highlight_style
is only applied when an item is actually selected.
I (a total Rust newbie) can't work out a good way to conditionally select an item in a SelectableList
... I think if SelectableList.select
took an Option<usize>
, everything would all just work, but since it takes just a usize
, I have to only call .select
if some condition is true, which means I have to do something ugly like this:
let mut w = tui::widgets::SelectableList::default();
let w = w.block(Block::default().borders(Borders::ALL))
.items(&["one", "two", "three"])
.highlight_style(tui::style::Style::default().fg(tui::style::Color::Cyan));
if let Some(n) = app.selected {
w.select(n)
} else {
w
}.render(t, &chunks[0]);
Is there (or could there be added) a better way to deal with conditional selection?
Hi I'm learning rust and I was wondering if you could help me with an issue I ran into when using this library. (great library btw)
Example code:
extern crate tui;
use tui::widgets::Block;
use tui::widgets::Chart;
const TITLE: &str = " CPU Usage ";
pub struct CPU {
pub chart: Chart<'static, String, String>,
}
impl CPU {
pub fn new() -> CPU {
let chart = Chart::default();
chart.block(Block::default().title(TITLE));
CPU {
chart,
}
}
}
I'm getting an issue due to the chart.block(Block::default().title(TITLE));
line. The error says
error[E0597]: `chart` does not live long enough
--> src/widgets/cpu.rs:15:9
|
15 | chart.block(Block::default().title(TITLE));
| ^^^^^ borrowed value does not live long enough
...
19 | }
| - borrowed value only lives until here
|
= note: borrowed value must be valid for the static lifetime...
but aren't I moving the chart
value into the CPU struct which is being returned? I'm confused why the borrowed value doesn't live longer. Sorry to bother you with a newb question, but I would appreciate any help. Thanks!
Hi there,
I was wondering if I could use Termion's AlternateScreen with tui-rs and I found myself adding the following to your code:
pub type AlternateScreenBackend =
TermionBackend<termion::screen::AlternateScreen<termion::raw::RawTerminal<io::Stdout>>>;
impl AlternateScreenBackend {
pub fn new() -> Result<AlternateScreenBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
let screen = termion::screen::AlternateScreen::from(raw);
Ok(TermionBackend { stdout: screen })
}
}
It worked just as I expected! But then, I faced another problem: I couldn't use print!
or even write to stdout
, I needed to write to backend.stdout
, but that would require some changes in the lib's API, since it's a private attribute.
Exposing it doesn't seem like a good solution for me. We could implement std::io::Write
for Terminal
and just call self.backend.stdout.write
internally.
What do you think about that? Is the change pertinent? Do you have another solution? I'm still in the "fighting the borrow-checker" phase, but I would love to help with that or any other feature.
Also, thanks for the great lib! :)
How about Scrollbars for Tables and similiar widgets or a related solution?
I have a long multi-line string that I continuously append onto and I'd like Paragraph
to append to the end. I'd then like Paragraph
to scroll down so that no lines get hidden from the newly appended text. However, I can't know how much to scroll because Paragraph
doesn't yield any information about its layouted text.
Minimal repro with 0.3.0-beta.3 (not a new one - I initially observed it with 0.2.3) and with termion 1.5.1: https://gitlab.com/karolinepauls/tui-repro/blob/master/src/main.rs
First the line gets drawn over the border, then when it reaches the screen boundary, it panics. I would expect .wrap(false)
to truncate the excess rather than print it.
Panic (cleaned up, the lines end up printed garbled over the TUI, with broken newlines due to the raw terminal mode):
thread 'main' panicked at 'Trying to access position outside the buffer: x=239, y=3, area=Rect { x: 0, y: 0, width: 239, height: 70 }', /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/buffer.rs:182:9
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::print at libstd/sys_common/backtrace.rs:71 at libstd/sys_common/backtrace.rs:59
2: std::panicking::default_hook::{{closure}} at libstd/panicking.rs:211
3: std::panicking::default_hook at libstd/panicking.rs:227
4: std::panicking::rust_panic_with_hook at libstd/panicking.rs:475
5: std::panicking::continue_panic_fmt at libstd/panicking.rs:390
6: std::panicking::begin_panic_fmt at libstd/panicking.rs:345
7: tui::buffer::Buffer::index_of at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/buffer.rs:182
8: tui::buffer::Buffer::get_mut at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/buffer.rs:149
9: <tui::widgets::paragraph::Paragraph<'a, 't, T> as tui::widgets::Widget>::draw
at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/widgets/paragraph.rs:197
10: <tui::terminal::Frame<'a, B>>::render at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/terminal.rs:40
11: tui::widgets::Widget::render at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/widgets/mod.rs: 82
12: tui_repro::main::{{closure}} at src/main.rs:31
13: <tui::terminal::Terminal<B>>::draw at /home/k/.cargo/registry/src/github.com-1ecc6299db9ec823/tui-0.3.0-beta.3/src/terminal.rs:126
14: tui_repro::main at src/main.rs:25
15: std::rt::lang_start::{{closure}} at /checkout/src/libstd/rt.rs:74
16: std::panicking::try::do_call at libstd/rt.rs:59 at libstd/panicking.rs:310
17: __rust_maybe_catch_panic at libpanic_unwind/lib.rs:105
18: std::rt::lang_start_internal at libstd/panicking.rs:289 at libstd/panic.rs:392 at libstd/rt.rs:58
19: std::rt::lang_start at /checkout/src/libstd/rt.rs:74
20: main
21: __libc_start_main
22: _start
I started playing around resizing the terminal and looking what happens. For now I've only fixed a couple of panics with higher resolutions: karolinepauls@bd33c04
However, I could still see some rendering artefacts and I'm investigating some right now. Artefacts appear when the number of characters exceeds 2^16 (resolution over 256x256). 2^16 characters is quite a lot but e.g. tmux continues working fine.
$ git grep ' as u16'
shows the lib generally assumes u16.
I haven't fixed the gauge demo is panicking so far because here the root of the problem is using integer percentages instead of floating points:
Line 81 in 56fc434
Limiting fractional sizes to 0-100 also likely makes scaling choppy.
One way would be to (A) hardcode max resolution to be X:Y where X * Y < 2^16. Another (B) - promote u16 dimensions to u32 (and limit the resolution to 2^32 in case someone's that crazy).
(C) Regarding percentages, I would rather see them as [0, 1] floating points, which would make slicing more precise (3 panes with a ratio of 0.33333333333... will leave a smaller gap than 3 panes with percentage being 33).
I'm looking for some input on that.
Today I was trying to use tui and so I went to try and walk through the examples. To my surprise I couldn't get them to work because MouseBackend
didn't seem to be a thing. After some investigation I figured this was due to the fact that master
was tracking development, and I had to go to the last release tag (0.1.3
) to find the compatible examples.
It might be more reasonable to create a devel
branch, and only merge changes to master
when the update is released. This way anyone coming to the GitHub repository immediately finds the correct source and examples.
It seems like the latest version of tui is not published to crates.io, making it awkward to depend on for published crates. (Perhaps the same is true for the termion crate?).
Would you consider publishing a new version? Thanks!
The auto-resizing implemented in #94 unfortunately makes it impossible to ensure that you're using the right size in your layout calculation.
Previously you'd use something like:
let new_size = terminal.size()?;
if size != new_size {
size = new_size;
terminal.resize(size)?;
}
terminal.draw(|mut f| { /* ... */ });
which ensures that the size
variable later used to calculate the layout is the same size that is used for the buffer inside the terminal
instance.
But now you have to do:
let size = terminal.size();
/* RACE WINDOW HERE */
terminal.draw(|mut f| { /* ... */ });
})
You have to ask for the size of the terminal first, and then, inside the draw() method, the size might change because of the automatic resizing. If the terminal is actually resized in between these two actions, the sizes get out of sync, and there's no way for the programmer to avoid the resulting artifacts or crash.
One way to handle this would be to adjust the callback in draw()
to accept the current terminal size as a second parameter like this:
terminal.draw(|mut f, size| { /* ... */ });
That would ensure that the size matches the buffer size. If that sounds good to you, I could prepare a PR for that.
If we use termion::color
at a Text inside of a widget, we will get a strange behaviour. For example:
format!(
" 0x00: {color_green}green{reset} foo {color_blue}blue{reset}",
color_green = COLOR_GREEN!(),
color_blue = COLOR_BLUE!(),
reset = color::Fg(color::Reset),
)
We expected a result like 0x00: green foo blue
, but we get it:
A workaround that I use (and works sometimes) is using cursor::Left
, for example:
format!(
" 0x00: {color_green}green{reset} {goto}foo {color_blue}{goto_blue}blue{reset}",
color_green = COLOR_GREEN!(),
color_blue = COLOR_BLUE!(),
goto = cursor::Left(14),
goto_blue = cursor::Left(19),
reset = color::Fg(color::Reset),
)
it will print:
But, this workaround isn't work in all situations, for example, if this text change.
Full example code:
extern crate termion;
extern crate tui;
use std::io::{stdout, stdin};
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::layout::{Layout, Constraint, Direction};
use termion::clear;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use tui::backend::Backend;
use tui::widgets::{Widget, Block, Borders, Text, List};
use tui::layout::Rect;
use tui::Frame;
use termion::{color, cursor};
macro_rules! COLOR_GREEN { () => { color::Fg(color::Green) } }
macro_rules! COLOR_BLUE { () => { color::Fg(color::LightBlue) } }
fn format_line(i: usize, text: &str) -> String {
format!(
" {:#04X}: {color_green}green{reset} {} {color_blue}blue{reset}",
i, text,
color_green = COLOR_GREEN!(),
color_blue = COLOR_BLUE!(),
reset = color::Fg(color::Reset),
)
}
fn draw_list<B>(f: &mut Frame<B>, area: Rect)
where
B: Backend,
{
let raw_text = ["foo", "bar"];
let formatted_text = raw_text
.iter()
.enumerate()
.map(|(i, text)|
Text::raw(
format_line(i, text)
)
);
List::new(formatted_text)
.block(Block::default().borders(Borders::ALL).title(" Table "))
.render(f, area);
}
fn main() -> Result<(), Box<std::error::Error>> {
let stdout_raw_mode = stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout_raw_mode);
let mut terminal = Terminal::new(backend)?;
let size = terminal.size()?;
println!("{}", clear::All);
'main: loop {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)].as_ref())
.split(size);
draw_list(&mut f, chunks[0]);
})?;
for c in stdin().keys() {
match c? {
Key::Char('q') => break 'main Ok(()),
_ => {}
}
}
}
}
Some types in this library (e.g. Style
, Block
) use mut self
in builder methods while others (e.g. SelectableList
) go with &'a mut self
. The latter style considered harmful, and I hate it a lot. This is what i had to make up to make things work for optional selection in a list view:
pub trait OptionalSelection<'a> {
fn select_optional(&'a mut self, index: Option<usize>) -> &'a mut Self;
}
impl<'a> OptionalSelection<'a> for SelectableList<'a> {
fn select_optional(&'a mut self, index: Option<usize>) -> &'a mut SelectableList<'a> {
match index {
Some(index) => self.select(index),
None => self,
}
}
}
Of course it's more about the lack of such method in the library (which only has select(&'a mut self, usize)
) than about the builder pattern and lifetimed references. But with a method that takes ownership and returns ownership, it's damn simple to store intermediate value somewhere and perform some conditional calls later. Try that with -> &'a mut Self
, I dare you!
Should I open a pull request about it? Could it be a breaking change? Is it acceptable?
catching sigint is a bit strange UX. (good old quit vim jokes anyone?)
that might be an unintentional side effect of terminal raw mode,
so i it would be ok to add a keyhandler for ctrl+c i guess, but Event::Input lacks a modifier.
So I'm following your examples and I notice in your main loop you create a new Group
with new List
's for each iteration. Looking at the code behind this at https://github.com/fdehau/tui-rs/blob/master/src/widgets/list.rs#L14 shows that List
has a Vec
inside of it. While all the items are references you'll still be allocating a Vec
for the entire thing. Looking at how its then used is to iterate over the items so you could probably come up with a custom iterator type that could take in. Looking further at the List example, you collect()
to a Vec
and then pass the slice in since List.items()
takes a slice. It then turns around an collect()
's to a Vec
again so you're taking the allocation performance hit twice.
Another idea would be to take the style independently of the data, both as IntoIterator
Then you could use Iterator.zip()
to combine the two. You could alternatively create a type like
pub enum Item<'s> {
Data(::std::fmt::Display), // allows any type that can be displayed to be used as input (so `String` and `&str` still work)
StyledData(::std::fmt::Display, &'s ::tui::style::Style),
}
Then List.items()
could have a signature like:
pub fn items<L: IntoIterator<Item=Item>(&'a mut self, items: L) -> &mut List<'a>
and you could just store the iterator inside of List
instead collecting to a Vec
. Then when draw()
ran it would just work with that iterator.
Currently terminal::Terminal
does not implement Debug
, which prohibit us from deriving Debug
trait for the struct that contains terminal::Terminal
.
#[derive(Debug) // Compile error: std::fmt::Debug is not implemented for Terminal
pub struct Render {
terminal: Terminal
}
This hurts interoperability as stated by the offical API guidelines.
I think it would be nice if we had an option for the chart widget to draw lines between each marker. I saw you were able to have a linechart in examples/demo.rs
but only when feeding in continuous data. Maybe add an option to the chart widget or create a new linechart struct to enable drawing a line between consecutive points. I could probably hack a pr together but I wasn't sure what the best way to implement it would be.
I'm trying to render a SelectableList
with white background and black text, but the list does not get those colors.
Instead only the text of the list items have the correct color, but the rest of the list has default background color.
I think this is because the SelectableList
creates a List
and renders it, but it fails to pass on the style
to it. The style
is only attached to the items; and when List
renders it calls Widget::background
with the default style before rendering the items.
The world map is really cool, any chance you could extend this so I can select Europe instead of the whole world? blessed-contrib currently has a USA map, but no map for Europe.
Thank you!
Is there a function which prevents one from scrolling outside the application? As it is still possible to use the terminal scrollbar to scroll up.
Or is this rather a question for the termion issues?
Group
layout is confusing. It has method render
like an ordinary widget, but it lacks a counterpart method draw
.
I tried to separate drawing logic from main App's single method and split it up into widgets, and call render
on them. For widgets i have to implement the draw
method โ the only one required by the trait. But no way! I can't, not without the reference to a terminal, which is needed for Group::render
.
impl Widget for MainViewController {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Min(0), Size::Fixed(3)])
.render( ??? );
}
}
I can see Group::render
accessing terminal's layout cache, but it doesn't make me feel any better. Any ideas?
The Table
type is giving me some type-related difficulty when I try to use it in a practical application.
What I want to do is display a formatted (read: truncated and aligned) float as one of the columns in a table, e.g.
HEADER1 HEADER2 THING
------- ------- -----
name other-thing 1.99
name2 description 25.80
Sounds simple, right? Not so fast. Granted, I am relatively new to Rust, so I may be missing something.
Row::Data(x)
expects x
to be an Iterator<D>
, where type D
is Display
. This means that every element must be the same type (no surprise) and that that type must implement Display
(also no surprise). Okay, so that means I need to provide all of my row values as some sort of string type. Maybe like this?
let data_vec = vec![
"name".to_string(),
"other-thing".to_string(),
format!("{:5.2}", 1.99)
];
let data_row = Row::Data(data_vec.into_iter());
Looks good so far. Now let's try to use it in a UI.
let size = term.size()?;
Group::default()
.sizes(&[Size::Percent(100)])
.render(term, &size, |t, c| {
Table::new(
vec!["HEADER1", "HEADER2", "THING"].into_iter(),
vec![data_row].into_iter()
).block(Block::default())
.render(t, &c[0]);
});
Hold on, what?
error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
--> src/main.rs:92:22
|
85 | let data_row = Row::Data(data_vec.into_iter());
| -------- captured outer variable
...
92 | vec![data_row].into_iter()
| ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure
I think that this is something ownership-related. Maybe we should use references. Let's change the first block to look like this:
let formatted_flt = format!("{:5.2}", 1.99);
let data_vec = vec![
"name",
"other-thing",
&formatted_flt
];
let data_row = Row::Data(data_vec.into_iter());
Nope.
error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
--> src/main.rs:94:22
|
87 | let data_row = Row::Data(data_vec.into_iter());
| -------- captured outer variable
...
94 | vec![data_row].into_iter()
| ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure
In the demos, this is avoided by using string literals as the ultimate source for the tabular data:
Lines 25 to 32 in ef2054a
Likewise in the docs:
Lines 30 to 35 in ef2054a
I really hope that the problem here is my understanding of the Rust type system. If not, then this is not a useful implementation of a user-facing table (ususally, tables are used to display information that is created or discovered at runtime).
my M(non-)WE with a .txt extension for Github's file uploader
I might be misunderstanding this whole thing, but it looks like the wrong comparison operator is used in Buffer::pos_of
.
Lines 170 to 176 in c561734
The index must be bigger than the buffer size instead of within the buffer size.
Given a buffer of 20*20 cells (length 400), I would expect 0 to be (0, 0), 20 to be (0, 1), and so on. Instead I must pass an index like 400 or 555.
I think there should be a Box shape just like there is a Line shape.
I tried to migrate one of my projects to the newly released v0.3.0-beta.0. For the most part things look good. The one thing that I had a problem with are the changes to Paragraph
, which now takes of a Vec<Text>
, and the fact that Text
only takes borrowed strings. It made it really hard to do things like:
let mut text = vec![];
if some_condition {
text.push(&format!("..."));
} else {
text.push(&format!("..."));
}
Paragraph::new(text.iter())...
due to lifetime issues (strings returned by format!()
don't live long enough). I think maybe Text
should store a Cow<str>
internally, so it could accept both borrowed and owned strings.
What do you think?
I might give it a shot on a branch an see how it goes...
https://docs.rs/crate/tui/0.1.1/builds/35335 It seems the documentation isn't available there due to a build error involving termbox. It would be nice to have the documentation accessible online, rather than having to get the crate and build it with cargo doc
Im very interested to see a TreeView as another widget. Is it planned or in consideration?
I'm getting all sorts of compiler puke when trying to use the List and SelectableList widgets. It's looking like the library requires me to use a fixed array to give to List or SelectableList. Why can't I use a vec? This makes it a PITA when dealing with dynamic UI lists.
So when implementing a full app it looks like the best practice is to implement different draw()
functions that encompass each group of widgets. You end up calling each draw()
inside the render()
closure, e.g.
Line 283 in b2bb24b
I ended up implementing my example app by implementing my first group and having the top level app loop call its draw()
. Then I put that group within another group and made a new draw()
calling the original like linked above. But the program immediately stopped working and didn't show anything. I was really confused for a but but then realized that my inner draw()
still had a t.draw()
call which caused the issue. There was no error or panic, the application just sat there with an empty screen. Looking at Terminal
there are a few things that look like they shouldn't be called at this inner level so maybe it'd be better to provide a wrapped type or a trait that just doesn't implement all these additional methods so people can't get tripped up by them.
When using a List or SelectableList, if an item contain newlines, text after the newline will get overwritten by the next items.
For example:
SelectableList::default()
.items(&["Hey\nYou",":O:O:O"])
.render(&mut f, size);
Renders as:
Hey
:O:O:O
When it should be rendered as:
Hey
You
:O:O:O
Hi @fdehau
Are you planning to add missing widgets from blessed-contrib like stacked-gauge donut etc.
It would also be nice to have some more shapes like triangles to draw on canvas widget.
I'm not getting the expected behavior of Text::raw
used in a widget::Paragraph
. It's not honoring \t
in the text, however, it is honoring \n
. Am I doing something wrong? Is there a reason this isn't working?
let x = format!("No indent\n");
let y = format!("\tThis should indent.");
let text = Text [Text::raw(&x), Text::raw(&y)];
Paragraph::new(text.iter())...
I expect the second line to be indented by a tab, but it's flush left with the first line start.
from my point of view, the dynamic rendering is complemented well with a dynamically build dispatch chain. This approach I have used in tui-logger-widget and IMHO works quite well. Just I would prefer, that this is an integrated part in tui-rs instead of the widget.
Have you thought about using similar dynamically built dispatch chain ?
Occurs when a buffer gets updated. Repro - karolinepauls@37f4bf8 (modified the paragraph example).
Artefacts clear when resizing.
I think the error may be in code calculating which characters to update. I want to look into it later, when I'm done with paragraph text reflow.
The current scrolling behavior of selectable lists somewhat confusing. Currently, when you're at the bottom of the window and you select the previous entry, instead of just moving the selection up, the list scrolls up and the selected row is still at the bottom. I'd much rather have the list only scroll when I already hit the top or bottom of the currently visible rows.
Additionally, I'd love to have support for a scroll offset. That is, a set amount of rows that is kept above or below the selected row, i.e. the list starts scrolling X rows before you hit the top/bottom. One reason is that this allows you to see whether there are still more rows to come even without a scrollbar.
Hi,
I've integrated a simple example from the doc which does nothing but display 2 blocks, and this example randomly displays overlapping boxes. Here's the code excerpt:
fn draw(t: &mut Terminal<RawBackend>) -> std::result::Result<(), std::io::Error> {
let size = t.size()?;
Group::default()
.direction(Direction::Vertical)
.margin(1)
.sizes(&[Size::Fixed(10), Size::Max(20), Size::Min(10)])
.render(t, &size, |t, chunks| {
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(t, &chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(t, &chunks[2]);
});
t.draw()
}
And here are the results - randomly one or the other (note that I always press ^L
before running the example):
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.