Proof of concept for a resizable viewport. The general approach here is to duplicate the `Terminal` struct from ratatui, but with our own logic. This is a "light fork" in that we are still using all the base ratatui functions (`Buffer`, `Widget` and so on), but we're doing our own bookkeeping at the top level to determine where to draw everything. This approach could use improvement—e.g, when the window is resized to a smaller size, if the UI wraps, we don't correctly clear out the artifacts from wrapping. This is possible with a little work (i.e. tracking what parts of our UI would have been wrapped), but this behavior is at least at par with the existing behavior. https://github.com/user-attachments/assets/4eb17689-09fd-4daa-8315-c7ebc654986d cc @joshka who might have Thoughts™
589 lines
22 KiB
Rust
589 lines
22 KiB
Rust
// This is derived from `ratatui::Terminal`, which is licensed under the following terms:
|
|
//
|
|
// The MIT License (MIT)
|
|
// Copyright (c) 2016-2022 Florian Dehau
|
|
// Copyright (c) 2023-2025 The Ratatui Developers
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
use std::io;
|
|
|
|
use ratatui::backend::Backend;
|
|
use ratatui::backend::ClearType;
|
|
use ratatui::buffer::Buffer;
|
|
use ratatui::layout::Position;
|
|
use ratatui::layout::Rect;
|
|
use ratatui::layout::Size;
|
|
use ratatui::widgets::StatefulWidget;
|
|
use ratatui::widgets::StatefulWidgetRef;
|
|
use ratatui::widgets::Widget;
|
|
use ratatui::widgets::WidgetRef;
|
|
|
|
#[derive(Debug, Hash)]
|
|
pub struct Frame<'a> {
|
|
/// 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()`.
|
|
pub(crate) cursor_position: Option<Position>,
|
|
|
|
/// The area of the viewport
|
|
pub(crate) viewport_area: Rect,
|
|
|
|
/// The buffer that is used to draw the current frame
|
|
pub(crate) buffer: &'a mut Buffer,
|
|
|
|
/// The frame count indicating the sequence number of this frame.
|
|
pub(crate) count: usize,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Frame<'_> {
|
|
/// The area of the current frame
|
|
///
|
|
/// This is guaranteed not to change during rendering, so may be called multiple times.
|
|
///
|
|
/// If your app listens for a resize event from the backend, it should ignore the values from
|
|
/// the event for any calculations that are used to render the current frame and use this value
|
|
/// instead as this is the area of the buffer that is used to render the current frame.
|
|
pub const fn area(&self) -> Rect {
|
|
self.viewport_area
|
|
}
|
|
|
|
/// Render a [`Widget`] to the current buffer using [`Widget::render`].
|
|
///
|
|
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
|
/// frame (which can be obtained using [`Layout`] to split the total area).
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # use ratatui::{backend::TestBackend, Terminal};
|
|
/// # let backend = TestBackend::new(5, 5);
|
|
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
/// # let mut frame = terminal.get_frame();
|
|
/// use ratatui::{layout::Rect, widgets::Block};
|
|
///
|
|
/// let block = Block::new();
|
|
/// let area = Rect::new(0, 0, 5, 5);
|
|
/// frame.render_widget(block, area);
|
|
/// ```
|
|
///
|
|
/// [`Layout`]: crate::layout::Layout
|
|
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
|
|
widget.render(area, self.buffer);
|
|
}
|
|
|
|
/// Render a [`WidgetRef`] to the current buffer using [`WidgetRef::render_ref`].
|
|
///
|
|
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
|
/// frame (which can be obtained using [`Layout`] to split the total area).
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # #[cfg(feature = "unstable-widget-ref")] {
|
|
/// # use ratatui::{backend::TestBackend, Terminal};
|
|
/// # let backend = TestBackend::new(5, 5);
|
|
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
/// # let mut frame = terminal.get_frame();
|
|
/// use ratatui::{layout::Rect, widgets::Block};
|
|
///
|
|
/// let block = Block::new();
|
|
/// let area = Rect::new(0, 0, 5, 5);
|
|
/// frame.render_widget_ref(block, area);
|
|
/// # }
|
|
/// ```
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub fn render_widget_ref<W: WidgetRef>(&mut self, widget: W, area: Rect) {
|
|
widget.render_ref(area, self.buffer);
|
|
}
|
|
|
|
/// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
|
|
///
|
|
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
|
/// frame (which can be obtained using [`Layout`] to split the total area).
|
|
///
|
|
/// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
|
|
/// given [`StatefulWidget`].
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # use ratatui::{backend::TestBackend, Terminal};
|
|
/// # let backend = TestBackend::new(5, 5);
|
|
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
/// # let mut frame = terminal.get_frame();
|
|
/// use ratatui::{
|
|
/// layout::Rect,
|
|
/// widgets::{List, ListItem, ListState},
|
|
/// };
|
|
///
|
|
/// let mut state = ListState::default().with_selected(Some(1));
|
|
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
|
|
/// let area = Rect::new(0, 0, 5, 5);
|
|
/// frame.render_stateful_widget(list, area, &mut state);
|
|
/// ```
|
|
///
|
|
/// [`Layout`]: crate::layout::Layout
|
|
pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
|
|
where
|
|
W: StatefulWidget,
|
|
{
|
|
widget.render(area, self.buffer, state);
|
|
}
|
|
|
|
/// Render a [`StatefulWidgetRef`] to the current buffer using
|
|
/// [`StatefulWidgetRef::render_ref`].
|
|
///
|
|
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
|
/// frame (which can be obtained using [`Layout`] to split the total area).
|
|
///
|
|
/// The last argument should be an instance of the [`StatefulWidgetRef::State`] associated to
|
|
/// the given [`StatefulWidgetRef`].
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # #[cfg(feature = "unstable-widget-ref")] {
|
|
/// # use ratatui::{backend::TestBackend, Terminal};
|
|
/// # let backend = TestBackend::new(5, 5);
|
|
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
/// # let mut frame = terminal.get_frame();
|
|
/// use ratatui::{
|
|
/// layout::Rect,
|
|
/// widgets::{List, ListItem, ListState},
|
|
/// };
|
|
///
|
|
/// let mut state = ListState::default().with_selected(Some(1));
|
|
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
|
|
/// let area = Rect::new(0, 0, 5, 5);
|
|
/// frame.render_stateful_widget_ref(list, area, &mut state);
|
|
/// # }
|
|
/// ```
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub fn render_stateful_widget_ref<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
|
|
where
|
|
W: StatefulWidgetRef,
|
|
{
|
|
widget.render_ref(area, self.buffer, 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_position`]. Pick one of the APIs and
|
|
/// stick with it.
|
|
///
|
|
/// [`Terminal::hide_cursor`]: crate::Terminal::hide_cursor
|
|
/// [`Terminal::show_cursor`]: crate::Terminal::show_cursor
|
|
/// [`Terminal::set_cursor_position`]: crate::Terminal::set_cursor_position
|
|
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) {
|
|
self.cursor_position = Some(position.into());
|
|
}
|
|
|
|
/// Gets the buffer that this `Frame` draws into as a mutable reference.
|
|
pub fn buffer_mut(&mut self) -> &mut Buffer {
|
|
self.buffer
|
|
}
|
|
|
|
/// Returns the current frame count.
|
|
///
|
|
/// This method provides access to the frame count, which is a sequence number indicating
|
|
/// how many frames have been rendered up to (but not including) this one. It can be used
|
|
/// for purposes such as animation, performance tracking, or debugging.
|
|
///
|
|
/// Each time a frame has been rendered, this count is incremented,
|
|
/// providing a consistent way to reference the order and number of frames processed by the
|
|
/// terminal. When count reaches its maximum value (`usize::MAX`), it wraps around to zero.
|
|
///
|
|
/// This count is particularly useful when dealing with dynamic content or animations where the
|
|
/// state of the display changes over time. By tracking the frame count, developers can
|
|
/// synchronize updates or changes to the content with the rendering process.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ratatui::{backend::TestBackend, Terminal};
|
|
/// # let backend = TestBackend::new(5, 5);
|
|
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
/// # let mut frame = terminal.get_frame();
|
|
/// let current_count = frame.count();
|
|
/// println!("Current frame count: {}", current_count);
|
|
/// ```
|
|
pub const fn count(&self) -> usize {
|
|
self.count
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
|
pub struct Terminal<B>
|
|
where
|
|
B: Backend,
|
|
{
|
|
/// The backend used to interface with the terminal
|
|
backend: B,
|
|
/// Holds the results of the current and previous draw calls. The two are compared at the end
|
|
/// of each draw pass to output the necessary updates to the terminal
|
|
buffers: [Buffer; 2],
|
|
/// Index of the current buffer in the previous array
|
|
current: usize,
|
|
/// Whether the cursor is currently hidden
|
|
hidden_cursor: bool,
|
|
/// Area of the viewport
|
|
pub viewport_area: Rect,
|
|
/// Last known size of the terminal. Used to detect if the internal buffers have to be resized.
|
|
pub last_known_screen_size: Size,
|
|
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
|
|
/// and the terminal resized.
|
|
pub last_known_cursor_pos: Position,
|
|
/// Number of frames rendered up until current time.
|
|
frame_count: usize,
|
|
}
|
|
|
|
impl<B> Drop for Terminal<B>
|
|
where
|
|
B: Backend,
|
|
{
|
|
#[allow(clippy::print_stderr)]
|
|
fn drop(&mut self) {
|
|
// Attempt to restore the cursor state
|
|
if self.hidden_cursor {
|
|
if let Err(err) = self.show_cursor() {
|
|
eprintln!("Failed to show the cursor: {err}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<B> Terminal<B>
|
|
where
|
|
B: Backend,
|
|
{
|
|
/// Creates a new [`Terminal`] with the given [`Backend`] and [`TerminalOptions`].
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// use std::io::stdout;
|
|
///
|
|
/// use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal, TerminalOptions, Viewport};
|
|
///
|
|
/// let backend = CrosstermBackend::new(stdout());
|
|
/// let viewport = Viewport::Fixed(Rect::new(0, 0, 10, 10));
|
|
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
|
|
/// # std::io::Result::Ok(())
|
|
/// ```
|
|
pub fn with_options(mut backend: B) -> io::Result<Self> {
|
|
let screen_size = backend.size()?;
|
|
let cursor_pos = backend.get_cursor_position()?;
|
|
Ok(Self {
|
|
backend,
|
|
buffers: [
|
|
Buffer::empty(Rect::new(0, 0, 0, 0)),
|
|
Buffer::empty(Rect::new(0, 0, 0, 0)),
|
|
],
|
|
current: 0,
|
|
hidden_cursor: false,
|
|
viewport_area: Rect::new(0, cursor_pos.y, 0, 0),
|
|
last_known_screen_size: screen_size,
|
|
last_known_cursor_pos: cursor_pos,
|
|
frame_count: 0,
|
|
})
|
|
}
|
|
|
|
/// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
|
pub fn get_frame(&mut self) -> Frame {
|
|
let count = self.frame_count;
|
|
Frame {
|
|
cursor_position: None,
|
|
viewport_area: self.viewport_area,
|
|
buffer: self.current_buffer_mut(),
|
|
count,
|
|
}
|
|
}
|
|
|
|
/// Gets the current buffer as a mutable reference.
|
|
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
|
|
&mut self.buffers[self.current]
|
|
}
|
|
|
|
/// Gets the backend
|
|
pub const fn backend(&self) -> &B {
|
|
&self.backend
|
|
}
|
|
|
|
/// Gets the backend as a mutable reference
|
|
pub fn backend_mut(&mut self) -> &mut B {
|
|
&mut self.backend
|
|
}
|
|
|
|
/// Obtains a difference between the previous and the current buffer and passes it to the
|
|
/// current backend for drawing.
|
|
pub fn flush(&mut self) -> io::Result<()> {
|
|
let previous_buffer = &self.buffers[1 - self.current];
|
|
let current_buffer = &self.buffers[self.current];
|
|
let updates = previous_buffer.diff(current_buffer);
|
|
if let Some((col, row, _)) = updates.last() {
|
|
self.last_known_cursor_pos = Position { x: *col, y: *row };
|
|
}
|
|
self.backend.draw(updates.into_iter())
|
|
}
|
|
|
|
/// Updates the Terminal so that internal buffers match the requested area.
|
|
///
|
|
/// Requested area will be saved to remain consistent when rendering. This leads to a full clear
|
|
/// of the screen.
|
|
pub fn resize(&mut self, screen_size: Size) -> io::Result<()> {
|
|
self.last_known_screen_size = screen_size;
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets the viewport area.
|
|
pub fn set_viewport_area(&mut self, area: Rect) {
|
|
self.buffers[self.current].resize(area);
|
|
self.buffers[1 - self.current].resize(area);
|
|
self.viewport_area = area;
|
|
}
|
|
|
|
/// Queries the backend for size and resizes if it doesn't match the previous size.
|
|
pub fn autoresize(&mut self) -> io::Result<()> {
|
|
let screen_size = self.size()?;
|
|
if screen_size != self.last_known_screen_size {
|
|
self.resize(screen_size)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Draws a single frame to the terminal.
|
|
///
|
|
/// Returns a [`CompletedFrame`] if successful, otherwise a [`std::io::Error`].
|
|
///
|
|
/// If the render callback passed to this method can fail, use [`try_draw`] instead.
|
|
///
|
|
/// Applications should call `draw` or [`try_draw`] in a loop to continuously render the
|
|
/// terminal. These methods are the main entry points for drawing to the terminal.
|
|
///
|
|
/// [`try_draw`]: Terminal::try_draw
|
|
///
|
|
/// This method will:
|
|
///
|
|
/// - autoresize the terminal if necessary
|
|
/// - call the render callback, passing it a [`Frame`] reference to render to
|
|
/// - flush the current internal state by copying the current buffer to the backend
|
|
/// - move the cursor to the last known position if it was set during the rendering closure
|
|
///
|
|
/// The render callback should fully render the entire frame when called, including areas that
|
|
/// are unchanged from the previous frame. This is because each frame is compared to the
|
|
/// previous frame to determine what has changed, and only the changes are written to the
|
|
/// terminal. If the render callback does not fully render the frame, the terminal will not be
|
|
/// in a consistent state.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
|
|
/// # let mut terminal = ratatui::Terminal::new(backend)?;
|
|
/// use ratatui::{layout::Position, widgets::Paragraph};
|
|
///
|
|
/// // with a closure
|
|
/// terminal.draw(|frame| {
|
|
/// let area = frame.area();
|
|
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
/// frame.set_cursor_position(Position { x: 0, y: 0 });
|
|
/// })?;
|
|
///
|
|
/// // or with a function
|
|
/// terminal.draw(render)?;
|
|
///
|
|
/// fn render(frame: &mut ratatui::Frame) {
|
|
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
|
|
/// }
|
|
/// # std::io::Result::Ok(())
|
|
/// ```
|
|
pub fn draw<F>(&mut self, render_callback: F) -> io::Result<()>
|
|
where
|
|
F: FnOnce(&mut Frame),
|
|
{
|
|
self.try_draw(|frame| {
|
|
render_callback(frame);
|
|
io::Result::Ok(())
|
|
})
|
|
}
|
|
|
|
/// Tries to draw a single frame to the terminal.
|
|
///
|
|
/// Returns [`Result::Ok`] containing a [`CompletedFrame`] if successful, otherwise
|
|
/// [`Result::Err`] containing the [`std::io::Error`] that caused the failure.
|
|
///
|
|
/// This is the equivalent of [`Terminal::draw`] but the render callback is a function or
|
|
/// closure that returns a `Result` instead of nothing.
|
|
///
|
|
/// Applications should call `try_draw` or [`draw`] in a loop to continuously render the
|
|
/// terminal. These methods are the main entry points for drawing to the terminal.
|
|
///
|
|
/// [`draw`]: Terminal::draw
|
|
///
|
|
/// This method will:
|
|
///
|
|
/// - autoresize the terminal if necessary
|
|
/// - call the render callback, passing it a [`Frame`] reference to render to
|
|
/// - flush the current internal state by copying the current buffer to the backend
|
|
/// - move the cursor to the last known position if it was set during the rendering closure
|
|
/// - return a [`CompletedFrame`] with the current buffer and the area of the terminal
|
|
///
|
|
/// The render callback passed to `try_draw` can return any [`Result`] with an error type that
|
|
/// can be converted into an [`std::io::Error`] using the [`Into`] trait. This makes it possible
|
|
/// to use the `?` operator to propagate errors that occur during rendering. If the render
|
|
/// callback returns an error, the error will be returned from `try_draw` as an
|
|
/// [`std::io::Error`] and the terminal will not be updated.
|
|
///
|
|
/// The [`CompletedFrame`] returned by this method can be useful for debugging or testing
|
|
/// purposes, but it is often not used in regular applicationss.
|
|
///
|
|
/// The render callback should fully render the entire frame when called, including areas that
|
|
/// are unchanged from the previous frame. This is because each frame is compared to the
|
|
/// previous frame to determine what has changed, and only the changes are written to the
|
|
/// terminal. If the render function does not fully render the frame, the terminal will not be
|
|
/// in a consistent state.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```should_panic
|
|
/// # use ratatui::layout::Position;;
|
|
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
|
|
/// # let mut terminal = ratatui::Terminal::new(backend)?;
|
|
/// use std::io;
|
|
///
|
|
/// use ratatui::widgets::Paragraph;
|
|
///
|
|
/// // with a closure
|
|
/// terminal.try_draw(|frame| {
|
|
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
|
|
/// let area = frame.area();
|
|
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
/// frame.set_cursor_position(Position { x: 0, y: 0 });
|
|
/// io::Result::Ok(())
|
|
/// })?;
|
|
///
|
|
/// // or with a function
|
|
/// terminal.try_draw(render)?;
|
|
///
|
|
/// fn render(frame: &mut ratatui::Frame) -> io::Result<()> {
|
|
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
|
|
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
|
|
/// Ok(())
|
|
/// }
|
|
/// # io::Result::Ok(())
|
|
/// ```
|
|
pub fn try_draw<F, E>(&mut self, render_callback: F) -> io::Result<()>
|
|
where
|
|
F: FnOnce(&mut Frame) -> Result<(), E>,
|
|
E: Into<io::Error>,
|
|
{
|
|
// Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
|
|
// and the terminal (if growing), which may OOB.
|
|
self.autoresize()?;
|
|
|
|
let mut frame = self.get_frame();
|
|
|
|
render_callback(&mut frame).map_err(Into::into)?;
|
|
|
|
// 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
|
|
// Buffer. 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(position) => {
|
|
self.show_cursor()?;
|
|
self.set_cursor_position(position)?;
|
|
}
|
|
}
|
|
|
|
self.swap_buffers();
|
|
|
|
// Flush
|
|
self.backend.flush()?;
|
|
|
|
// increment frame count before returning from draw
|
|
self.frame_count = self.frame_count.wrapping_add(1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Hides the cursor.
|
|
pub fn hide_cursor(&mut self) -> io::Result<()> {
|
|
self.backend.hide_cursor()?;
|
|
self.hidden_cursor = true;
|
|
Ok(())
|
|
}
|
|
|
|
/// Shows the cursor.
|
|
pub fn show_cursor(&mut self) -> io::Result<()> {
|
|
self.backend.show_cursor()?;
|
|
self.hidden_cursor = false;
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets the current cursor position.
|
|
///
|
|
/// This is the position of the cursor after the last draw call.
|
|
#[allow(dead_code)]
|
|
pub fn get_cursor_position(&mut self) -> io::Result<Position> {
|
|
self.backend.get_cursor_position()
|
|
}
|
|
|
|
/// Sets the cursor position.
|
|
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
|
|
let position = position.into();
|
|
self.backend.set_cursor_position(position)?;
|
|
self.last_known_cursor_pos = position;
|
|
Ok(())
|
|
}
|
|
|
|
/// Clear the terminal and force a full redraw on the next draw call.
|
|
pub fn clear(&mut self) -> io::Result<()> {
|
|
if self.viewport_area.is_empty() {
|
|
return Ok(());
|
|
}
|
|
self.backend
|
|
.set_cursor_position(self.viewport_area.as_position())?;
|
|
self.backend.clear_region(ClearType::AfterCursor)?;
|
|
// Reset the back buffer to make sure the next update will redraw everything.
|
|
self.buffers[1 - self.current].reset();
|
|
Ok(())
|
|
}
|
|
|
|
/// Clears the inactive buffer and swaps it with the current buffer
|
|
pub fn swap_buffers(&mut self) {
|
|
self.buffers[1 - self.current].reset();
|
|
self.current = 1 - self.current;
|
|
}
|
|
|
|
/// Queries the real size of the backend.
|
|
pub fn size(&self) -> io::Result<Size> {
|
|
self.backend.size()
|
|
}
|
|
}
|