Good evening!
I'm trying to write a very simple terminal application that draws two textboxes on screen, accepting input on one and showing output on the other, using Rust and tui-rs. The first part works perfectly, but my problems arose when i tried to draw two blocks at the same time: for some reason, it only shows the second block (in order of drawing) and if i move my mouse, it flickers between the two in a weird way. My best guess is that this is due to my drawing implementation, which somehow "clears" the screen whenever it needs to draw something, but if that's the case, i couldn't find any doc on it, and i wouldn't know how to go about working around this. I've provided some code that should be enough to replicate the issue on a smaller scale.
#![allow(unused_imports)]
#![allow(unused_variables)]
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::io
use tui::{
backend::CrosstermBackend,
layout::Rect,
widgets::{Block, Borders},
Terminal,
};
struct FirstStruct {}
impl FirstStruct {
pub fn draw(&self, term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
term.draw(|f| {
let size = f.size();
let (w, h) = (size.width / 2, size.height);
let (x, y) = (size.x, size.y);
let rect = Rect::new(x, y, w, h);
let block = Block::default()
.title("One")
.borders(Borders::ALL);
f.render_widget(block, rect)
})?;
Ok(())
}
}
struct SecondStruct { }
impl SecondStruct {
pub fn draw(&self, term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
term.draw(|f| {
let size = f.size();
let (w, h) = (size.width / 2, size.height);
let (x, y) = (size.x + w, size.y);
let rect = Rect::new(x, y, w, h);
let block = Block::default()
.title("Two")
.borders(Borders::ALL);
f.render_widget(block, rect)
})?;
Ok(())
}
}
fn main() -> io::Result<()>{
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let first = FirstStruct {};
let second = SecondStruct {};
let mut running = true;
while running {
if let Event::Key(key) = event::read()? {
running = false;
}
second.draw(&mut terminal)?;
first.draw(&mut terminal)?;
}
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}
Does anybody know how i can fix this issue? Thanks in advance!!
Every time you call Terminal::draw()
, you must draw everything that you want to be visible at once. Instead of passing Terminal
to your own draw
functions, pass the Frame
that you get from Terminal::draw()
. That is, replace
second.draw(&mut terminal)?;
first.draw(&mut terminal)?;
with
terminal.draw(|f| {
first.draw(f)?;
second.draw(f)?;
});
and change the signature of FirstStruct
and SecondStruct
to match.
Also, it would be more usual to, instead of computing the rectangle for each widget in the individual functions, decide at the top level (using Layout
, perhaps) and pass Rect
s down to the drawing functions. That way, they can be positioned differently in different situations. What you have will work, but it's not as easy to change.
Layout code from the documentation's example, adjusted to your situation:
terminal.draw(|f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(50),
Constraint::Percentage(50),
].as_ref()
)
.split(f.size());
first.draw(f, chunks[0])?;
second.draw(f, chunks[1])?;
});