mirror of https://github.com/helix-editor/helix
Port to termwiz: compiles but no rendering yet
parent
20a132e36f
commit
7a51085e8a
@ -1,197 +0,0 @@
|
|||||||
use crate::{backend::Backend, buffer::Cell};
|
|
||||||
use crossterm::{
|
|
||||||
cursor::{CursorShape, Hide, MoveTo, SetCursorShape, Show},
|
|
||||||
execute, queue,
|
|
||||||
style::{
|
|
||||||
Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
|
|
||||||
SetForegroundColor,
|
|
||||||
},
|
|
||||||
terminal::{self, Clear, ClearType},
|
|
||||||
};
|
|
||||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
|
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
pub struct CrosstermBackend<W: Write> {
|
|
||||||
buffer: W,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> CrosstermBackend<W>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
pub fn new(buffer: W) -> CrosstermBackend<W> {
|
|
||||||
CrosstermBackend { buffer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Write for CrosstermBackend<W>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.buffer.write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.buffer.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Backend for CrosstermBackend<W>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
|
||||||
{
|
|
||||||
let mut fg = Color::Reset;
|
|
||||||
let mut bg = Color::Reset;
|
|
||||||
let mut modifier = Modifier::empty();
|
|
||||||
let mut last_pos: Option<(u16, u16)> = None;
|
|
||||||
for (x, y, cell) in content {
|
|
||||||
// Move the cursor if the previous location was not (x - 1, y)
|
|
||||||
if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
|
|
||||||
map_error(queue!(self.buffer, MoveTo(x, y)))?;
|
|
||||||
}
|
|
||||||
last_pos = Some((x, y));
|
|
||||||
if cell.modifier != modifier {
|
|
||||||
let diff = ModifierDiff {
|
|
||||||
from: modifier,
|
|
||||||
to: cell.modifier,
|
|
||||||
};
|
|
||||||
diff.queue(&mut self.buffer)?;
|
|
||||||
modifier = cell.modifier;
|
|
||||||
}
|
|
||||||
if cell.fg != fg {
|
|
||||||
let color = CColor::from(cell.fg);
|
|
||||||
map_error(queue!(self.buffer, SetForegroundColor(color)))?;
|
|
||||||
fg = cell.fg;
|
|
||||||
}
|
|
||||||
if cell.bg != bg {
|
|
||||||
let color = CColor::from(cell.bg);
|
|
||||||
map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
|
|
||||||
bg = cell.bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
map_error(queue!(self.buffer, Print(&cell.symbol)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
map_error(queue!(
|
|
||||||
self.buffer,
|
|
||||||
SetForegroundColor(CColor::Reset),
|
|
||||||
SetBackgroundColor(CColor::Reset),
|
|
||||||
SetAttribute(CAttribute::Reset)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
|
||||||
map_error(execute!(self.buffer, Hide))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
|
|
||||||
let shape = match kind {
|
|
||||||
CursorKind::Block => CursorShape::Block,
|
|
||||||
CursorKind::Bar => CursorShape::Line,
|
|
||||||
CursorKind::Underline => CursorShape::UnderScore,
|
|
||||||
CursorKind::Hidden => unreachable!(),
|
|
||||||
};
|
|
||||||
map_error(execute!(self.buffer, Show, SetCursorShape(shape)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
|
||||||
crossterm::cursor::position()
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
|
||||||
map_error(execute!(self.buffer, MoveTo(x, y)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) -> io::Result<()> {
|
|
||||||
map_error(execute!(self.buffer, Clear(ClearType::All)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> io::Result<Rect> {
|
|
||||||
let (width, height) =
|
|
||||||
terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(Rect::new(0, 0, width, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.buffer.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
|
|
||||||
error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ModifierDiff {
|
|
||||||
pub from: Modifier,
|
|
||||||
pub to: Modifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModifierDiff {
|
|
||||||
fn queue<W>(&self, mut w: W) -> io::Result<()>
|
|
||||||
where
|
|
||||||
W: io::Write,
|
|
||||||
{
|
|
||||||
//use crossterm::Attribute;
|
|
||||||
let removed = self.from - self.to;
|
|
||||||
if removed.contains(Modifier::REVERSED) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::BOLD) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
|
||||||
if self.to.contains(Modifier::DIM) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::ITALIC) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::UNDERLINED) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::DIM) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::CROSSED_OUT) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let added = self.to - self.from;
|
|
||||||
if added.contains(Modifier::REVERSED) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::BOLD) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::ITALIC) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::UNDERLINED) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::DIM) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::CROSSED_OUT) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::SLOW_BLINK) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
|
|
||||||
}
|
|
||||||
if added.contains(Modifier::RAPID_BLINK) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
use crate::buffer::Cell;
|
|
||||||
|
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
|
||||||
|
|
||||||
#[cfg(feature = "crossterm")]
|
|
||||||
mod crossterm;
|
|
||||||
#[cfg(feature = "crossterm")]
|
|
||||||
pub use self::crossterm::CrosstermBackend;
|
|
||||||
|
|
||||||
mod test;
|
|
||||||
pub use self::test::TestBackend;
|
|
||||||
|
|
||||||
pub trait Backend {
|
|
||||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>;
|
|
||||||
fn hide_cursor(&mut self) -> Result<(), io::Error>;
|
|
||||||
fn show_cursor(&mut self, kind: CursorKind) -> Result<(), io::Error>;
|
|
||||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
|
|
||||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
|
|
||||||
fn clear(&mut self) -> Result<(), io::Error>;
|
|
||||||
fn size(&self) -> Result<Rect, io::Error>;
|
|
||||||
fn flush(&mut self) -> Result<(), io::Error>;
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
backend::Backend,
|
|
||||||
buffer::{Buffer, Cell},
|
|
||||||
};
|
|
||||||
use helix_core::unicode::width::UnicodeWidthStr;
|
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
|
||||||
use std::{fmt::Write, io};
|
|
||||||
|
|
||||||
/// A backend used for the integration tests.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TestBackend {
|
|
||||||
width: u16,
|
|
||||||
buffer: Buffer,
|
|
||||||
height: u16,
|
|
||||||
cursor: bool,
|
|
||||||
pos: (u16, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a string representation of the given buffer for debugging purpose.
|
|
||||||
fn buffer_view(buffer: &Buffer) -> String {
|
|
||||||
let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3);
|
|
||||||
for cells in buffer.content.chunks(buffer.area.width as usize) {
|
|
||||||
let mut overwritten = vec![];
|
|
||||||
let mut skip: usize = 0;
|
|
||||||
view.push('"');
|
|
||||||
for (x, c) in cells.iter().enumerate() {
|
|
||||||
if skip == 0 {
|
|
||||||
view.push_str(&c.symbol);
|
|
||||||
} else {
|
|
||||||
overwritten.push((x, &c.symbol))
|
|
||||||
}
|
|
||||||
skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1);
|
|
||||||
}
|
|
||||||
view.push('"');
|
|
||||||
if !overwritten.is_empty() {
|
|
||||||
write!(
|
|
||||||
&mut view,
|
|
||||||
" Hidden by multi-width symbols: {:?}",
|
|
||||||
overwritten
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
view.push('\n');
|
|
||||||
}
|
|
||||||
view
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestBackend {
|
|
||||||
pub fn new(width: u16, height: u16) -> TestBackend {
|
|
||||||
TestBackend {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
buffer: Buffer::empty(Rect::new(0, 0, width, height)),
|
|
||||||
cursor: false,
|
|
||||||
pos: (0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer(&self) -> &Buffer {
|
|
||||||
&self.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, width: u16, height: u16) {
|
|
||||||
self.buffer.resize(Rect::new(0, 0, width, height));
|
|
||||||
self.width = width;
|
|
||||||
self.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert_buffer(&self, expected: &Buffer) {
|
|
||||||
assert_eq!(expected.area, self.buffer.area);
|
|
||||||
let diff = expected.diff(&self.buffer);
|
|
||||||
if diff.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut debug_info = String::from("Buffers are not equal");
|
|
||||||
debug_info.push('\n');
|
|
||||||
debug_info.push_str("Expected:");
|
|
||||||
debug_info.push('\n');
|
|
||||||
let expected_view = buffer_view(expected);
|
|
||||||
debug_info.push_str(&expected_view);
|
|
||||||
debug_info.push('\n');
|
|
||||||
debug_info.push_str("Got:");
|
|
||||||
debug_info.push('\n');
|
|
||||||
let view = buffer_view(&self.buffer);
|
|
||||||
debug_info.push_str(&view);
|
|
||||||
debug_info.push('\n');
|
|
||||||
|
|
||||||
debug_info.push_str("Diff:");
|
|
||||||
debug_info.push('\n');
|
|
||||||
let nice_diff = diff
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (x, y, cell))| {
|
|
||||||
let expected_cell = expected.get(*x, *y);
|
|
||||||
format!(
|
|
||||||
"{}: at ({}, {}) expected {:?} got {:?}",
|
|
||||||
i, x, y, expected_cell, cell
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n");
|
|
||||||
debug_info.push_str(&nice_diff);
|
|
||||||
panic!("{}", debug_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Backend for TestBackend {
|
|
||||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
|
||||||
{
|
|
||||||
for (x, y, c) in content {
|
|
||||||
self.buffer[(x, y)] = c.clone();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_cursor(&mut self) -> Result<(), io::Error> {
|
|
||||||
self.cursor = false;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_cursor(&mut self, _kind: CursorKind) -> Result<(), io::Error> {
|
|
||||||
self.cursor = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error> {
|
|
||||||
Ok(self.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error> {
|
|
||||||
self.pos = (x, y);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) -> Result<(), io::Error> {
|
|
||||||
self.buffer.reset();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> Result<Rect, io::Error> {
|
|
||||||
Ok(Rect::new(0, 0, self.width, self.height))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<(), io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,225 +0,0 @@
|
|||||||
use crate::{backend::Backend, buffer::Buffer};
|
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
/// UNSTABLE
|
|
||||||
enum ResizeBehavior {
|
|
||||||
Fixed,
|
|
||||||
Auto,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
/// UNSTABLE
|
|
||||||
pub struct Viewport {
|
|
||||||
area: Rect,
|
|
||||||
resize_behavior: ResizeBehavior,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Viewport {
|
|
||||||
/// UNSTABLE
|
|
||||||
pub fn fixed(area: Rect) -> Viewport {
|
|
||||||
Viewport {
|
|
||||||
area,
|
|
||||||
resize_behavior: ResizeBehavior::Fixed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
/// Options to pass to [`Terminal::with_options`]
|
|
||||||
pub struct TerminalOptions {
|
|
||||||
/// Viewport used to draw to the terminal
|
|
||||||
pub viewport: Viewport,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interface to the terminal backed by Termion
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Terminal<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
/// Kind of cursor (hidden or others)
|
|
||||||
cursor_kind: CursorKind,
|
|
||||||
/// Viewport
|
|
||||||
viewport: Viewport,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> Drop for Terminal<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Attempt to restore the cursor state
|
|
||||||
if self.cursor_kind == CursorKind::Hidden {
|
|
||||||
if let Err(err) = self.show_cursor(CursorKind::Block) {
|
|
||||||
eprintln!("Failed to show the cursor: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> Terminal<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
/// 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()?;
|
|
||||||
Terminal::with_options(
|
|
||||||
backend,
|
|
||||||
TerminalOptions {
|
|
||||||
viewport: Viewport {
|
|
||||||
area: size,
|
|
||||||
resize_behavior: ResizeBehavior::Auto,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UNSTABLE
|
|
||||||
pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
|
|
||||||
Ok(Terminal {
|
|
||||||
backend,
|
|
||||||
buffers: [
|
|
||||||
Buffer::empty(options.viewport.area),
|
|
||||||
Buffer::empty(options.viewport.area),
|
|
||||||
],
|
|
||||||
current: 0,
|
|
||||||
cursor_kind: CursorKind::Block,
|
|
||||||
viewport: options.viewport,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// 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,
|
|
||||||
// cursor_position: None,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
|
|
||||||
&mut self.buffers[self.current]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backend(&self) -> &B {
|
|
||||||
&self.backend
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
self.backend.draw(updates.into_iter())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the Terminal so that internal buffers match the requested size. Requested size will
|
|
||||||
/// be saved so the size can remain consistent when rendering.
|
|
||||||
/// This leads to a full clear of the screen.
|
|
||||||
pub fn resize(&mut self, area: Rect) -> io::Result<()> {
|
|
||||||
self.buffers[self.current].resize(area);
|
|
||||||
self.buffers[1 - self.current].resize(area);
|
|
||||||
self.viewport.area = area;
|
|
||||||
self.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries the backend for size and resizes if it doesn't match the previous size.
|
|
||||||
pub fn autoresize(&mut self) -> io::Result<Rect> {
|
|
||||||
let size = self.size()?;
|
|
||||||
if size != self.viewport.area {
|
|
||||||
self.resize(size)?;
|
|
||||||
};
|
|
||||||
Ok(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
|
|
||||||
/// and prepares for the next draw call.
|
|
||||||
pub fn draw(
|
|
||||||
&mut self,
|
|
||||||
cursor_position: Option<(u16, u16)>,
|
|
||||||
cursor_kind: CursorKind,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
// // 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();
|
|
||||||
// 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()?;
|
|
||||||
|
|
||||||
if let Some((x, y)) = cursor_position {
|
|
||||||
self.set_cursor(x, y)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
match cursor_kind {
|
|
||||||
CursorKind::Hidden => self.hide_cursor()?,
|
|
||||||
kind => self.show_cursor(kind)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap buffers
|
|
||||||
self.buffers[1 - self.current].reset();
|
|
||||||
self.current = 1 - self.current;
|
|
||||||
|
|
||||||
// Flush
|
|
||||||
self.backend.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cursor_kind(&self) -> CursorKind {
|
|
||||||
self.cursor_kind
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hide_cursor(&mut self) -> io::Result<()> {
|
|
||||||
self.backend.hide_cursor()?;
|
|
||||||
self.cursor_kind = CursorKind::Hidden;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
|
|
||||||
self.backend.show_cursor(kind)?;
|
|
||||||
self.cursor_kind = kind;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
|
||||||
self.backend.get_cursor()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
|
||||||
self.backend.set_cursor(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the terminal and force a full redraw on the next draw call.
|
|
||||||
pub fn clear(&mut self) -> io::Result<()> {
|
|
||||||
self.backend.clear()?;
|
|
||||||
// Reset the back buffer to make sure the next update will redraw everything.
|
|
||||||
self.buffers[1 - self.current].reset();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries the real size of the backend.
|
|
||||||
pub fn size(&self) -> io::Result<Rect> {
|
|
||||||
self.backend.size()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue