gyscos / cursive

A Text User Interface library for the Rust programming language
MIT License
4.24k stars 242 forks source link

[BUG] cursive with crossterm or termion backends redraws the screen every time #667

Open vi opened 2 years ago

vi commented 2 years ago

Describe the bug

With default ncurses backend, cursive applications feel smooth and does not blink in e.g. Alacritty. But with other backends (crossterm and termion), it seems like it just redraws the entire screen each time something is updated.

Also in scenario where ncurses backend write only 11 KB to the tty, but with the pure-Rust backends I see 174 KB of data. This may become problematic if TUI applications are accessed over SSH with bad network.

To Reproduce

  1. Build some cursive application with multiple backends.
Example application ``` extern crate cursive_table_view; use std::cmp::Ordering; use cursive::{ align::HAlign, traits::{Nameable, Resizable}, views::{TextView, LinearLayout, TextArea}, Cursive, With, }; use cursive_table_view::{TableView, TableViewItem}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum MyColumn { Name, Count, Rate, } #[derive(Clone, Debug)] struct Foo { name: String, count: usize, rate: usize, } impl TableViewItem for Foo { fn to_column(&self, column: MyColumn) -> String { match column { MyColumn::Name => self.name.to_string(), MyColumn::Count => format!("{}", self.count), MyColumn::Rate => format!("{}", self.rate), } } fn cmp(&self, other: &Self, column: MyColumn) -> Ordering where Self: Sized, { match column { MyColumn::Name => self.name.cmp(&other.name), MyColumn::Count => self.count.cmp(&other.count), MyColumn::Rate => self.rate.cmp(&other.rate), } } } fn main() { let mut siv = cursive::default(); // Configure the actual table let table = TableView::::new() .column(MyColumn::Name, "Name", |c| c.width(20)) .column(MyColumn::Count, "Count", |c| c.align(HAlign::Center)) .column(MyColumn::Rate, "Rate", |c| { c.ordering(Ordering::Greater).align(HAlign::Right).width(20) }) .default_column(MyColumn::Name) .with(|table| { table.insert_item(Foo { count: 3, name: "QQQ".to_owned(), rate: 4, }); table.insert_item(Foo { count: 2, name: "WWW".to_owned(), rate: 40, }); table.insert_item(Foo { count: 0, name: "EEE".to_owned(), rate: 0, }); }) .on_submit(|siv: &mut Cursive, _row: usize, index: usize| { siv.call_on_name("q", |t: &mut TableView| { if let Some(i) = t.borrow_item_mut(index) { i.count += 1; } }); }); let linear_layout = LinearLayout::vertical() .child(TextView::new("Top of the page")) .child(TextArea::new().fixed_height(8).full_width()) .child(table.with_name("q").full_height().full_width()); siv.add_fullscreen_layer(linear_layout.full_screen()); siv.run(); } ```
  1. Run those multiple versions on Linux (e.g. in Alacritty + tmux)
  2. Compare how the feel

Expected behavior

Applications differ only by their filesize and dynamic dependencies, maybe some tricky feature support.

Environment

Other notes

If this cannot be fixed easily, then it should be documented in "Backends" wiki article. This blinking and tty bandwidth usage may be overseen by deploying developer, who might think that by switching to crossterm/termion one just gains easy deployment without any apparent loss in functionality.

gyscos commented 2 years ago

Hi - indeed, right now we re-draw the screen on any change or event.

ncurses includes some internal caching to only re-draw the changes, which the other backends don't do at the moment. However, there exists a third-party backend wrapper that brings this feature: https://github.com/agavrilov/cursive_buffered_backend

Eventually I plan on bringing this in cursive itself, but for now this wrapper may help your performance issue.

vi commented 2 years ago

Tried cursive_buffered_backend - flickering disappeared and bytes written to tty dropped from 174kb to 15kb. Both with crossterm and termion underlying backends. Also works with curses::n backend, but with no visible change.

hrkfdn commented 2 years ago

@gyscos Would a PR to integrate this into the Termion Backend help?

gyscos commented 2 years ago

The buffered backend would benefit both the termion and crossterm backends, and really could be integrated around the Printer level. The Printer itself may benefit from a bit of re-structuring too (as discussed a bit in #652), so I might include some buffering like that when I get there.