Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

Control cursor position and visibility via Frame #309

Merged
merged 1 commit into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion examples/barchart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion examples/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/crossterm_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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') => {
Expand Down
2 changes: 1 addition & 1 deletion examples/curses_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion examples/gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion examples/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion examples/paragraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion examples/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion examples/rustbox_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 1 addition & 1 deletion examples/sparkline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/termion_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 19 additions & 17 deletions examples/user_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
fdehau marked this conversation as resolved.
Show resolved Hide resolved
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()
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
40 changes: 36 additions & 4 deletions src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand All @@ -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()?;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion tests/widgets_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions tests/widgets_chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/widgets_gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading