mirror of
https://github.com/ratatui-org/ratatui
synced 2025-02-16 14:08:44 +00:00
parent
2b48409cfd
commit
8c2ee0ed85
30 changed files with 102 additions and 58 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -2,7 +2,17 @@
|
|||
|
||||
## To be released
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* `Terminal::draw()` now requires a closure that takes `&mut Frame`.
|
||||
|
||||
### Features
|
||||
|
||||
* Add feature-gated (`serde`) serialization of `Style`.
|
||||
* Add `Frame::set_cursor()` to dictate where the cursor should be placed after
|
||||
the call to `Terminial::draw()`. Calling this method would also make the
|
||||
cursor visible after the call to `draw()`. See example usage in
|
||||
*examples/user_input.rs*.
|
||||
|
||||
## v0.9.5 - 2020-05-21
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ authors = ["Florian Dehau <work@fdehau.com>"]
|
|||
description = """
|
||||
A library to build rich terminal user interfaces or dashboards
|
||||
"""
|
||||
documentation = "https://docs.rs/tui/0.9.5/tui/"
|
||||
documentation = "https://docs.rs/tui/0.10.0/tui/"
|
||||
keywords = ["tui", "terminal", "dashboard"]
|
||||
repository = "https://github.com/fdehau/tui-rs"
|
||||
license = "MIT"
|
||||
|
|
|
@ -70,7 +70,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
|
|
|
@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let events = Events::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
// Wrapping block for a group
|
||||
// Just draw the block and the group on the same area and build the group
|
||||
// with at least a margin of 1
|
||||
|
|
|
@ -92,7 +92,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
|
|
|
@ -79,7 +79,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let size = f.size();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
|
|
@ -73,7 +73,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
terminal.clear()?;
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| ui::draw(&mut f, &mut app))?;
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
match rx.recv()? {
|
||||
Event::Input(event) => match event.code {
|
||||
KeyCode::Char('q') => {
|
||||
|
|
|
@ -40,7 +40,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
loop {
|
||||
terminal.draw(|mut f| ui::draw(&mut f, &mut app))?;
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
if let Some(input) = terminal.backend_mut().get_curses_mut().get_input() {
|
||||
match input {
|
||||
easycurses::Input::Character(c) => {
|
||||
|
|
|
@ -42,7 +42,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let events = Events::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let size = f.size();
|
||||
let label = Label::default().text("Test");
|
||||
f.render_widget(label, size);
|
||||
|
|
|
@ -63,7 +63,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
|
|
|
@ -23,7 +23,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let events = Events::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
|
|
|
@ -88,7 +88,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
|
|
|
@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
let mut scroll: u16 = 0;
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let size = f.size();
|
||||
|
||||
// Words made "loooong" to demonstrate line breaking.
|
||||
|
|
|
@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let events = Events::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let size = f.size();
|
||||
|
||||
let chunks = Layout::default()
|
||||
|
|
|
@ -34,7 +34,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
loop {
|
||||
terminal.draw(|mut f| ui::draw(&mut f, &mut app))?;
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
if let Ok(rustbox::Event::KeyEvent(key)) =
|
||||
terminal.backend().rustbox().peek_event(tick_rate, false)
|
||||
{
|
||||
|
|
|
@ -65,7 +65,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut app = App::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
|
|
|
@ -88,7 +88,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
// Input
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let rects = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.margin(5)
|
||||
|
|
|
@ -37,7 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
// Main loop
|
||||
loop {
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let size = f.size();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
|
|
@ -39,7 +39,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
let mut app = App::new("Termion demo", cli.enhanced_graphics);
|
||||
loop {
|
||||
terminal.draw(|mut f| ui::draw(&mut f, &mut app))?;
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
|
||||
match events.next()? {
|
||||
Event::Input(key) => match key {
|
||||
|
|
|
@ -14,13 +14,8 @@
|
|||
mod util;
|
||||
|
||||
use crate::util::event::{Event, Events};
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{self, Write},
|
||||
};
|
||||
use termion::{
|
||||
cursor::Goto, event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen,
|
||||
};
|
||||
use std::{error::Error, io};
|
||||
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
|
@ -71,7 +66,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
loop {
|
||||
// Draw UI
|
||||
terminal.draw(|mut f| {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
|
@ -98,6 +93,22 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.style(Style::default().fg(Color::Yellow))
|
||||
.block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
f.render_widget(input, chunks[1]);
|
||||
match app.input_mode {
|
||||
InputMode::Normal =>
|
||||
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
|
||||
{}
|
||||
|
||||
InputMode::Editing => {
|
||||
// Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering
|
||||
f.set_cursor(
|
||||
// Put cursor past the end of the input text
|
||||
chunks[1].x + app.input.width() as u16 + 1,
|
||||
// Move one line down, from the border to the input line
|
||||
chunks[1].y + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let messages = app
|
||||
.messages
|
||||
.iter()
|
||||
|
@ -108,15 +119,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
f.render_widget(messages, chunks[2]);
|
||||
})?;
|
||||
|
||||
// Put the cursor back inside the input box
|
||||
write!(
|
||||
terminal.backend_mut(),
|
||||
"{}",
|
||||
Goto(4 + app.input.width() as u16, 5)
|
||||
)?;
|
||||
// stdout is buffered, flush it to see the effect immediately when hitting backspace
|
||||
io::stdout().flush().ok();
|
||||
|
||||
// Handle input
|
||||
if let Event::Input(input) = events.next()? {
|
||||
match app.input_mode {
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
//! let stdout = io::stdout().into_raw_mode()?;
|
||||
//! let backend = TermionBackend::new(stdout);
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! terminal.draw(|mut f| {
|
||||
//! terminal.draw(|f| {
|
||||
//! let size = f.size();
|
||||
//! let block = Block::default()
|
||||
//! .title("Block")
|
||||
|
@ -116,7 +116,7 @@
|
|||
//! let stdout = io::stdout().into_raw_mode()?;
|
||||
//! let backend = TermionBackend::new(stdout);
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! terminal.draw(|mut f| {
|
||||
//! terminal.draw(|f| {
|
||||
//! let chunks = Layout::default()
|
||||
//! .direction(Direction::Vertical)
|
||||
//! .margin(1)
|
||||
|
|
|
@ -29,6 +29,12 @@ where
|
|||
B: Backend,
|
||||
{
|
||||
terminal: &'a mut Terminal<B>,
|
||||
|
||||
/// Where should the cursor be after drawing this frame?
|
||||
///
|
||||
/// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x,
|
||||
/// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`.
|
||||
cursor_position: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
impl<'a, B> Frame<'a, B>
|
||||
|
@ -95,6 +101,16 @@ where
|
|||
{
|
||||
widget.render(area, self.terminal.current_buffer_mut(), state);
|
||||
}
|
||||
|
||||
/// After drawing this frame, make the cursor visible and put it at the specified (x, y)
|
||||
/// coordinates. If this method is not called, the cursor will be hidden.
|
||||
///
|
||||
/// Note that this will interfere with calls to `Terminal::hide_cursor()`,
|
||||
/// `Terminal::show_cursor()`, and `Terminal::set_cursor()`. Pick one of the APIs and stick
|
||||
/// with it.
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) {
|
||||
self.cursor_position = Some((x, y));
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Drop for Terminal<B>
|
||||
|
@ -115,7 +131,7 @@ impl<B> Terminal<B>
|
|||
where
|
||||
B: Backend,
|
||||
{
|
||||
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
|
||||
/// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
|
||||
/// default colors for the foreground and the background
|
||||
pub fn new(backend: B) -> io::Result<Terminal<B>> {
|
||||
let size = backend.size()?;
|
||||
|
@ -130,7 +146,10 @@ where
|
|||
|
||||
/// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
||||
pub fn get_frame(&mut self) -> Frame<B> {
|
||||
Frame { terminal: self }
|
||||
Frame {
|
||||
terminal: self,
|
||||
cursor_position: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
|
||||
|
@ -178,17 +197,30 @@ where
|
|||
/// and prepares for the next draw call.
|
||||
pub fn draw<F>(&mut self, f: F) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(Frame<B>),
|
||||
F: FnOnce(&mut Frame<B>),
|
||||
{
|
||||
// Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
|
||||
// and the terminal (if growing), which may OOB.
|
||||
self.autoresize()?;
|
||||
|
||||
f(self.get_frame());
|
||||
let mut frame = self.get_frame();
|
||||
f(&mut frame);
|
||||
// We can't change the cursor position right away because we have to flush the frame to
|
||||
// stdout first. But we also can't keep the frame around, since it holds a &mut to
|
||||
// Terminal. Thus, we're taking the important data out of the Frame and dropping it.
|
||||
let cursor_position = frame.cursor_position;
|
||||
|
||||
// Draw to stdout
|
||||
self.flush()?;
|
||||
|
||||
match cursor_position {
|
||||
None => self.hide_cursor()?,
|
||||
Some((x, y)) => {
|
||||
self.show_cursor()?;
|
||||
self.set_cursor(x, y)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap buffers
|
||||
self.buffers[1 - self.current].reset();
|
||||
self.current = 1 - self.current;
|
||||
|
|
|
@ -184,7 +184,7 @@ pub trait Widget {
|
|||
/// ]);
|
||||
///
|
||||
/// loop {
|
||||
/// terminal.draw(|mut f| {
|
||||
/// terminal.draw(|f| {
|
||||
/// // The items managed by the application are transformed to something
|
||||
/// // that is understood by tui.
|
||||
/// let items = events.items.iter().map(Text::raw);
|
||||
|
|
|
@ -10,7 +10,7 @@ fn widgets_block_renders() {
|
|||
let backend = TestBackend::new(10, 10);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let block = Block::default()
|
||||
.title("Title")
|
||||
.borders(Borders::ALL)
|
||||
|
|
|
@ -13,7 +13,7 @@ fn widgets_chart_can_have_axis_with_zero_length_bounds() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let datasets = [Dataset::default()
|
||||
.marker(symbols::Marker::Braille)
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
|
@ -42,7 +42,7 @@ fn widgets_chart_handles_overflows() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let datasets = [Dataset::default()
|
||||
.marker(symbols::Marker::Braille)
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
|
@ -79,7 +79,7 @@ fn widgets_chart_can_have_empty_datasets() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let datasets = [Dataset::default().data(&[]).graph_type(Line)];
|
||||
let chart = Chart::default()
|
||||
.block(
|
||||
|
|
|
@ -11,7 +11,7 @@ fn widgets_gauge_renders() {
|
|||
let backend = TestBackend::new(40, 10);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
|
|
|
@ -15,7 +15,7 @@ fn widgets_list_should_highlight_the_selected_item() {
|
|||
let mut state = ListState::default();
|
||||
state.select(Some(1));
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let items = vec![
|
||||
Text::raw("Item 1"),
|
||||
|
@ -71,7 +71,7 @@ fn widgets_list_should_truncate_items() {
|
|||
state.select(case.selected);
|
||||
let items = case.items.drain(..);
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let list = List::new(items)
|
||||
.block(Block::default().borders(Borders::RIGHT))
|
||||
.highlight_symbol(">> ");
|
||||
|
|
|
@ -18,7 +18,7 @@ fn widgets_paragraph_can_wrap_its_content() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let text = [Text::raw(SAMPLE_STRING)];
|
||||
let paragraph = Paragraph::new(text.iter())
|
||||
|
@ -85,7 +85,7 @@ fn widgets_paragraph_renders_double_width_graphemes() {
|
|||
|
||||
let s = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、";
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let text = [Text::raw(s)];
|
||||
let paragraph = Paragraph::new(text.iter())
|
||||
|
@ -117,7 +117,7 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
|
|||
|
||||
let s = "aコンピュータ上で文字を扱う場合、";
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let text = [Text::raw(s)];
|
||||
let paragraph = Paragraph::new(text.iter())
|
||||
|
|
|
@ -11,7 +11,7 @@ fn widgets_table_column_spacing_can_be_changed() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = Table::new(
|
||||
["Head1", "Head2", "Head3"].iter(),
|
||||
|
@ -112,7 +112,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = Table::new(
|
||||
["Head1", "Head2", "Head3"].iter(),
|
||||
|
@ -203,7 +203,7 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = Table::new(
|
||||
["Head1", "Head2", "Head3"].iter(),
|
||||
|
@ -312,7 +312,7 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = Table::new(
|
||||
["Head1", "Head2", "Head3"].iter(),
|
||||
|
|
|
@ -5,7 +5,7 @@ fn widgets_tabs_should_not_panic_on_narrow_areas() {
|
|||
let backend = TestBackend::new(1, 1);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let tabs = Tabs::default().titles(&["Tab1", "Tab2"]);
|
||||
f.render_widget(
|
||||
tabs,
|
||||
|
@ -27,7 +27,7 @@ fn widgets_tabs_should_truncate_the_last_item() {
|
|||
let backend = TestBackend::new(10, 1);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
.draw(|f| {
|
||||
let tabs = Tabs::default().titles(&["Tab1", "Tab2"]);
|
||||
f.render_widget(
|
||||
tabs,
|
||||
|
|
Loading…
Add table
Reference in a new issue