Skip to content

Commit

Permalink
feat!: add get/set_cursor_position() methods to Terminal and Backend (
Browse files Browse the repository at this point in the history
#1284)

The new methods return/accept `Into<Position>` which can be either a Position or a (u16, u16) tuple.

```rust
backend.set_cursor_position(Position { x: 0, y: 20 })?;
let position = backend.get_cursor_position()?;
terminal.set_cursor_position((0, 20))?;
let position = terminal.set_cursor_position()?;
```
  • Loading branch information
EdJoPaTo authored Aug 6, 2024
1 parent afe1534 commit c68ee6c
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 90 deletions.
15 changes: 14 additions & 1 deletion BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:

- [v0.28.0](#v0280) (unreleased)
`Backend::size` returns `Size` instead of `Rect`
- `Backend` trait migrates to `get/set_cursor_position`
- Ratatui now requires Crossterm 0.28.0
- `Axis::labels` now accepts `IntoIterator<Into<Line>>`
- `Layout::init_cache` no longer returns bool and takes a `NonZeroUsize` instead of `usize`
- `ratatui::terminal` module is now private
- `Axis::labels` now accepts `IntoIterator<Into<Line>>`
- `ToText` no longer has a lifetime
- [v0.27.0](#v0270)
- List no clamps the selected index to list
Expand Down Expand Up @@ -71,6 +73,17 @@ This is a quick summary of the sections below:
The `Backend::size` method returns a `Size` instead of a `Rect`.
There is no need for the position here as it was always 0,0.

### `Backend` trait migrates to `get/set_cursor_position` ([#1284])

[#1284]: https://github.com/ratatui-org/ratatui/pull/1284

If you just use the types implementing the `Backend` trait, you will see deprecation hints but
nothing is a breaking change for you.

If you implement the Backend trait yourself, you need to update the implementation and add the
`get/set_cursor_position` method. You can remove the `get/set_cursor` methods as they are deprecated
and a default implementation for them exists.

### Ratatui now requires Crossterm 0.28.0 ([#1278])

[#1278]: https://github.com/ratatui-org/ratatui/pull/1278
Expand Down
29 changes: 13 additions & 16 deletions examples/user_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use ratatui::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
},
layout::{Constraint, Layout},
layout::{Constraint, Layout, Position},
style::{Color, Modifier, Style, Stylize},
text::{Line, Span, Text},
widgets::{Block, List, ListItem, Paragraph},
Expand Down Expand Up @@ -245,22 +245,19 @@ fn ui(f: &mut Frame, app: &App) {
.block(Block::bordered().title("Input"));
f.render_widget(input, input_area);
match app.input_mode {
InputMode::Normal =>
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
{}
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
InputMode::Normal => {}

InputMode::Editing => {
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
// rendering
#[allow(clippy::cast_possible_truncation)]
f.set_cursor(
// Draw the cursor at the current position in the input field.
// This position is can be controlled via the left and right arrow key
input_area.x + app.character_index as u16 + 1,
// Move one line down, from the border to the input line
input_area.y + 1,
);
}
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
// rendering
#[allow(clippy::cast_possible_truncation)]
InputMode::Editing => f.set_cursor_position(Position::new(
// Draw the cursor at the current position in the input field.
// This position is can be controlled via the left and right arrow key
input_area.x + app.character_index as u16 + 1,
// Move one line down, from the border to the input line
input_area.y + 1,
)),
}

let messages: Vec<ListItem> = app
Expand Down
46 changes: 33 additions & 13 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ use std::io;

use strum::{Display, EnumString};

use crate::{buffer::Cell, layout::Size};
use crate::{
buffer::Cell,
layout::{Position, Size},
};

#[cfg(feature = "termion")]
mod termion;
Expand Down Expand Up @@ -192,25 +195,25 @@ pub trait Backend {
/// # std::io::Result::Ok(())
/// ```
///
/// [`show_cursor`]: Backend::show_cursor
/// [`show_cursor`]: Self::show_cursor
fn hide_cursor(&mut self) -> io::Result<()>;

/// Show the cursor on the terminal screen.
///
/// See [`hide_cursor`] for an example.
///
/// [`hide_cursor`]: Backend::hide_cursor
/// [`hide_cursor`]: Self::hide_cursor
fn show_cursor(&mut self) -> io::Result<()>;

/// Get the current cursor position on the terminal screen.
///
/// The returned tuple contains the x and y coordinates of the cursor. The origin
/// (0, 0) is at the top left corner of the screen.
/// The returned tuple contains the x and y coordinates of the cursor.
/// The origin (0, 0) is at the top left corner of the screen.
///
/// See [`set_cursor`] for an example.
/// See [`set_cursor_position`] for an example.
///
/// [`set_cursor`]: Backend::set_cursor
fn get_cursor(&mut self) -> io::Result<(u16, u16)>;
/// [`set_cursor_position`]: Self::set_cursor_position
fn get_cursor_position(&mut self) -> io::Result<Position>;

/// Set the cursor position on the terminal screen to the given x and y coordinates.
///
Expand All @@ -220,14 +223,31 @@ pub trait Backend {
///
/// ```rust
/// # use ratatui::backend::{Backend, TestBackend};
/// # use ratatui::layout::Position;
/// # let mut backend = TestBackend::new(80, 25);
/// backend.set_cursor(10, 20)?;
/// assert_eq!(backend.get_cursor()?, (10, 20));
/// backend.set_cursor_position(Position { x: 10, y: 20 })?;
/// assert_eq!(backend.get_cursor_position()?, Position { x: 10, y: 20 });
/// # std::io::Result::Ok(())
/// ```
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()>;

/// Get the current cursor position on the terminal screen.
///
/// [`get_cursor`]: Backend::get_cursor
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()>;
/// The returned tuple contains the x and y coordinates of the cursor. The origin
/// (0, 0) is at the top left corner of the screen.
#[deprecated = "the method get_cursor_position indicates more clearly what about the cursor to get"]
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
let Position { x, y } = self.get_cursor_position()?;
Ok((x, y))
}

/// Set the cursor position on the terminal screen to the given x and y coordinates.
///
/// The origin (0, 0) is at the top left corner of the screen.
#[deprecated = "the method set_cursor_position indicates more clearly what about the cursor to set"]
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
self.set_cursor_position(Position { x, y })
}

/// Clears the whole terminal screen
///
Expand Down Expand Up @@ -261,7 +281,7 @@ pub trait Backend {
/// This method will return an error if the terminal screen could not be cleared. It will also
/// return an error if the `clear_type` is not supported by the backend.
///
/// [`clear`]: Backend::clear
/// [`clear`]: Self::clear
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
match clear_type {
ClearType::All => self.clear(),
Expand Down
6 changes: 4 additions & 2 deletions src/backend/crossterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,14 @@ where
execute!(self.writer, Show)
}

fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
fn get_cursor_position(&mut self) -> io::Result<Position> {
crossterm::cursor::position()
.map(|(x, y)| Position { x, y })
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
}

fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
let Position { x, y } = position.into();
execute!(self.writer, MoveTo(x, y))
}

Expand Down
8 changes: 5 additions & 3 deletions src/backend/termion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,13 @@ where
self.writer.flush()
}

fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer).map(|(x, y)| (x - 1, y - 1))
fn get_cursor_position(&mut self) -> io::Result<Position> {
termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer)
.map(|(x, y)| Position { x: x - 1, y: y - 1 })
}

fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
let Position { x, y } = position.into();
write!(self.writer, "{}", termion::cursor::Goto(x + 1, y + 1))?;
self.writer.flush()
}
Expand Down
10 changes: 7 additions & 3 deletions src/backend/termwiz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,16 @@ impl Backend for TermwizBackend {
Ok(())
}

fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
fn get_cursor_position(&mut self) -> io::Result<crate::layout::Position> {
let (x, y) = self.buffered_terminal.cursor_position();
Ok((x as u16, y as u16))
Ok((x as u16, y as u16).into())
}

fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
fn set_cursor_position<P: Into<crate::layout::Position>>(
&mut self,
position: P,
) -> io::Result<()> {
let crate::layout::Position { x, y } = position.into();
self.buffered_terminal.add_change(Change::CursorPosition {
x: Position::Absolute(x as usize),
y: Position::Absolute(y as usize),
Expand Down
Loading

0 comments on commit c68ee6c

Please sign in to comment.