Compare commits
4 Commits
61127339bc
...
a2146a0168
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | a2146a0168 | |
Savanni D'Gerinel | 54b34d81ec | |
Savanni D'Gerinel | 8f4d424d1d | |
Savanni D'Gerinel | 4a62372fd3 |
|
@ -1,195 +1,22 @@
|
||||||
use crossterm::{
|
mod ui;
|
||||||
event::{self, KeyCode, KeyEvent, KeyModifiers},
|
mod state;
|
||||||
terminal::{disable_raw_mode, enable_raw_mode},
|
mod types;
|
||||||
};
|
|
||||||
|
use ui::Canvas;
|
||||||
|
use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
use state::AppState;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env, io,
|
||||||
fs::File,
|
|
||||||
io::{self, BufRead, BufReader, Read},
|
|
||||||
path::PathBuf,
|
|
||||||
sync::mpsc,
|
sync::mpsc,
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tui::{
|
use tui::{backend::CrosstermBackend, Terminal};
|
||||||
backend::CrosstermBackend,
|
|
||||||
layout::{Alignment, Constraint, Direction, Layout},
|
|
||||||
style::{Color, Style},
|
|
||||||
widgets::{Block, BorderType, Borders, Paragraph},
|
|
||||||
Terminal,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TITLE: &str = "Text Editor Challenge";
|
// const TITLE: &str = "Text Editor Challenge";
|
||||||
const COPYRIGHT: &str = "(c) Savanni D'Gerinel - all rights reserved";
|
// const COPYRIGHT: &str = "(c) Savanni D'Gerinel - all rights reserved";
|
||||||
const TICK_RATE_MS: u64 = 200;
|
const TICK_RATE_MS: u64 = 200;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Document {
|
|
||||||
rows: Vec<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
fn contents(&self) -> String {
|
|
||||||
self.rows.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_length(&self, idx: usize) -> usize {
|
|
||||||
self.rows[idx].len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_count(&self) -> usize {
|
|
||||||
self.rows.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct AppState {
|
|
||||||
path: Option<PathBuf>,
|
|
||||||
cursor: Cursor,
|
|
||||||
contents: Document, // Obviously this is bad, but it's also only temporary.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
fn open(path: PathBuf) -> Self {
|
|
||||||
let mut file = File::open(path.clone()).unwrap();
|
|
||||||
let mut reader = BufReader::new(file);
|
|
||||||
let contents = reader
|
|
||||||
.lines()
|
|
||||||
.collect::<Result<Vec<String>, std::io::Error>>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
path: Some(path),
|
|
||||||
cursor: Default::default(),
|
|
||||||
contents: Document{ rows: contents },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_up(&mut self) {
|
|
||||||
self.cursor.cursor_up(&self.contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_down(&mut self) {
|
|
||||||
self.cursor.cursor_down(&self.contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_right(&mut self) {
|
|
||||||
self.cursor.cursor_right(&self.contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_left(&mut self) {
|
|
||||||
self.cursor.cursor_left();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Cursor {
|
|
||||||
row: usize,
|
|
||||||
column: usize,
|
|
||||||
desired_column: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cursor {
|
|
||||||
fn cursor_up(&mut self, doc: &Document) {
|
|
||||||
if self.row > 0 {
|
|
||||||
self.row -= 1;
|
|
||||||
}
|
|
||||||
self.correct_columns(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_down(&mut self, doc: &Document) {
|
|
||||||
if self.row < doc.row_count() - 1 {
|
|
||||||
self.row += 1;
|
|
||||||
}
|
|
||||||
self.correct_columns(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn correct_columns(&mut self, doc: &Document) {
|
|
||||||
let row_len = doc.row_length(self.row);
|
|
||||||
if self.desired_column < row_len {
|
|
||||||
self.column = self.desired_column;
|
|
||||||
} else {
|
|
||||||
self.column = row_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_right(&mut self, doc: &Document) {
|
|
||||||
if self.column < doc.row_length(self.row) {
|
|
||||||
self.column += 1;
|
|
||||||
}
|
|
||||||
self.desired_column = self.column;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_left(&mut self) {
|
|
||||||
if self.column > 0 {
|
|
||||||
self.column -= 1;
|
|
||||||
}
|
|
||||||
self.desired_column = self.column;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<T>(app_state: &AppState, terminal: &mut Terminal<T>) -> Result<(), anyhow::Error>
|
|
||||||
where
|
|
||||||
T: tui::backend::Backend,
|
|
||||||
{
|
|
||||||
terminal.draw(|rect| {
|
|
||||||
let size = rect.size();
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.margin(2)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Length(3),
|
|
||||||
Constraint::Min(2),
|
|
||||||
Constraint::Length(3),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(size);
|
|
||||||
|
|
||||||
let title = Paragraph::new(
|
|
||||||
app_state
|
|
||||||
.path
|
|
||||||
.clone()
|
|
||||||
.map(|path| path.to_string_lossy().to_owned().into())
|
|
||||||
.unwrap_or("No file opened".to_owned()),
|
|
||||||
)
|
|
||||||
.style(Style::default().fg(Color::LightCyan))
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.title("Copyright")
|
|
||||||
.border_type(BorderType::Plain),
|
|
||||||
);
|
|
||||||
rect.render_widget(title, chunks[0]);
|
|
||||||
|
|
||||||
let cp = Paragraph::new(COPYRIGHT)
|
|
||||||
.style(Style::default().fg(Color::LightCyan))
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.title("Copyright")
|
|
||||||
.border_type(BorderType::Plain),
|
|
||||||
);
|
|
||||||
rect.render_widget(cp, chunks[2]);
|
|
||||||
|
|
||||||
// TODO: rework scrolling as soon as I have a working cursor
|
|
||||||
let contents = Paragraph::new(app_state.contents.contents()).scroll((0, 0));
|
|
||||||
rect.render_widget(contents, chunks[1]);
|
|
||||||
|
|
||||||
// TODO: keeping track of the index of the top row and subtract that from the cursor row.
|
|
||||||
let row = app_state.cursor.row as u16;
|
|
||||||
let column = app_state.cursor.column as u16;
|
|
||||||
rect.set_cursor(chunks[1].x + column, chunks[1].y + row);
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Event<I> {
|
enum Event<I> {
|
||||||
Input(I),
|
Input(I),
|
||||||
|
@ -209,45 +36,30 @@ fn handle_input(tx: mpsc::Sender<Event<KeyEvent>>, tick_rate: Duration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if last_tick.elapsed() >= tick_rate {
|
if last_tick.elapsed() >= tick_rate && tx.send(Event::Tick).is_ok() {
|
||||||
if let Ok(_) = tx.send(Event::Tick) {
|
|
||||||
last_tick = Instant::now();
|
last_tick = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn app_loop<T>(
|
fn app_loop<T>(
|
||||||
mut app_state: AppState,
|
mut app_state: AppState,
|
||||||
terminal: &mut Terminal<T>,
|
mut screen: Canvas<T>,
|
||||||
rx: mpsc::Receiver<Event<KeyEvent>>,
|
rx: mpsc::Receiver<Event<KeyEvent>>,
|
||||||
) -> Result<(), anyhow::Error>
|
) -> Result<(), anyhow::Error>
|
||||||
where
|
where
|
||||||
T: tui::backend::Backend,
|
T: tui::backend::Backend,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
render(&app_state, terminal)?;
|
screen.render(&app_state)?;
|
||||||
|
|
||||||
let event = rx.recv()?;
|
match rx.recv()? {
|
||||||
|
Event::Input(event)
|
||||||
match event {
|
if event.code == KeyCode::Char('x') && event.modifiers == KeyModifiers::CONTROL =>
|
||||||
Event::Input(KeyEvent { code, modifiers })
|
|
||||||
if code == KeyCode::Char('x') && modifiers == KeyModifiers::CONTROL =>
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Event::Input(KeyEvent { code, .. }) if code == KeyCode::Down => {
|
Event::Input(event) => app_state.handle_event(event),
|
||||||
app_state.cursor_down();
|
|
||||||
}
|
|
||||||
Event::Input(KeyEvent { code, .. }) if code == KeyCode::Up => {
|
|
||||||
app_state.cursor_up();
|
|
||||||
}
|
|
||||||
Event::Input(KeyEvent { code, .. }) if code == KeyCode::Right => {
|
|
||||||
app_state.cursor_right();
|
|
||||||
}
|
|
||||||
Event::Input(KeyEvent { code, .. }) if code == KeyCode::Left => {
|
|
||||||
app_state.cursor_left();
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,17 +83,14 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
handle_input(tx, tick_rate);
|
handle_input(tx, tick_rate);
|
||||||
});
|
});
|
||||||
|
|
||||||
enable_raw_mode()?;
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
let _ = terminal.clear()?;
|
terminal.clear()?;
|
||||||
|
|
||||||
let result = app_loop(app_state, &mut terminal, rx);
|
let screen = Canvas::new(terminal);
|
||||||
|
|
||||||
disable_raw_mode()?;
|
let result = app_loop(app_state, screen, rx);
|
||||||
terminal.show_cursor()?;
|
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
|
use crate::types::{Cursor, Document};
|
||||||
|
use std::{fs::File, io::{BufRead, BufReader}, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
pub cursor: Cursor,
|
||||||
|
pub contents: Document, // Obviously this is bad, but it's also only temporary.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn open(path: PathBuf) -> Self {
|
||||||
|
let file = File::open(path.clone()).unwrap();
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let contents = reader
|
||||||
|
.lines()
|
||||||
|
.collect::<Result<Vec<String>, std::io::Error>>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: Some(path),
|
||||||
|
cursor: Default::default(),
|
||||||
|
contents: Document::new(contents),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_up(&mut self) {
|
||||||
|
self.cursor.cursor_up(&self.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_down(&mut self) {
|
||||||
|
self.cursor.cursor_down(&self.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_right(&mut self) {
|
||||||
|
self.cursor.cursor_right(&self.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_left(&mut self) {
|
||||||
|
self.cursor.cursor_left();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(&mut self, event: KeyEvent) {
|
||||||
|
let KeyEvent { code, .. }: KeyEvent = event;
|
||||||
|
match code {
|
||||||
|
KeyCode::Down => {
|
||||||
|
self.cursor_down();
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
self.cursor_up();
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
self.cursor_right();
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
self.cursor_left();
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
self.contents.backspace(&mut self.cursor);
|
||||||
|
}
|
||||||
|
KeyCode::Delete => {
|
||||||
|
self.contents.delete_at(&mut self.cursor);
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
self.contents.new_line(&mut self.cursor);
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
self.contents.insert_at(&mut self.cursor, c);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
|
||||||
|
// TODO: I'm increasingly feeling that cursors are per-document, not per-application. So I think I
|
||||||
|
// want to move the cursor into here, and then rendering requires asking for the cursor for the
|
||||||
|
// current document.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Document {
|
||||||
|
rows: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Document {
|
||||||
|
pub fn new(contents: Vec<String>) -> Self {
|
||||||
|
if contents.len() > (u16::MAX.into()) {
|
||||||
|
panic!("Document row count exceeds u16::MAX. The current scrolling code cannot handle that.");
|
||||||
|
}
|
||||||
|
Self{
|
||||||
|
rows: contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contents(&self) -> String {
|
||||||
|
self.rows.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_length(&self, idx: usize) -> usize {
|
||||||
|
self.rows[idx].len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_count(&self) -> usize {
|
||||||
|
self.rows.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_at(&mut self, cursor: &mut Cursor, c: char){
|
||||||
|
let (row, column) = cursor.addr();
|
||||||
|
|
||||||
|
self.rows[row].insert(column, c);
|
||||||
|
cursor.cursor_right(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn backspace(&mut self, cursor: &mut Cursor) {
|
||||||
|
let (row, column) = cursor.addr();
|
||||||
|
if cursor.column > 0 {
|
||||||
|
let _ = self.rows[row].remove(column - 1);
|
||||||
|
cursor.cursor_left();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_at(&mut self, cursor: &mut Cursor) {
|
||||||
|
let (row, column) = cursor.addr();
|
||||||
|
if cursor.column < self.rows[row].len() {
|
||||||
|
self.rows[row].remove(column);
|
||||||
|
cursor.correct_columns(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_line(&mut self, cursor: &mut Cursor) {
|
||||||
|
// when doing a newline, take everything to the right of the cursor from the current line
|
||||||
|
// and move it to the next line.
|
||||||
|
let (row, _) = cursor.addr();
|
||||||
|
|
||||||
|
self.rows.insert(row, String::new());
|
||||||
|
cursor.cursor_down(&self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Cursor {
|
||||||
|
row: usize,
|
||||||
|
column: usize,
|
||||||
|
desired_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub fn addr(&self) -> (usize, usize) {
|
||||||
|
(self.row, self.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_up(&mut self, doc: &Document) {
|
||||||
|
if self.row > 0 {
|
||||||
|
self.row -= 1;
|
||||||
|
}
|
||||||
|
self.correct_columns(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_down(&mut self, doc: &Document) {
|
||||||
|
if self.row < doc.row_count() - 1 {
|
||||||
|
self.row += 1;
|
||||||
|
}
|
||||||
|
self.correct_columns(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn correct_columns(&mut self, doc: &Document) {
|
||||||
|
let row_len = doc.row_length(self.row);
|
||||||
|
if self.desired_column < row_len {
|
||||||
|
self.column = self.desired_column;
|
||||||
|
} else {
|
||||||
|
self.column = row_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_right(&mut self, doc: &Document) {
|
||||||
|
if self.column < doc.row_length(self.row) {
|
||||||
|
self.column += 1;
|
||||||
|
}
|
||||||
|
self.desired_column = self.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_left(&mut self) {
|
||||||
|
if self.column > 0 {
|
||||||
|
self.column -= 1;
|
||||||
|
}
|
||||||
|
self.desired_column = self.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
|
use tui::{layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Block, BorderType, Borders, Paragraph}, Terminal};
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub struct Canvas<T: tui::backend::Backend> {
|
||||||
|
top_row: usize,
|
||||||
|
terminal: Terminal<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: tui::backend::Backend> Canvas<T> {
|
||||||
|
pub fn new(terminal: Terminal<T>) -> Self {
|
||||||
|
enable_raw_mode().unwrap();
|
||||||
|
Self {
|
||||||
|
top_row: 0,
|
||||||
|
terminal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self, app_state: &AppState) -> Result<(), anyhow::Error>
|
||||||
|
{
|
||||||
|
self.terminal.draw(|rect| {
|
||||||
|
let size = rect.size();
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(2)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Min(2),
|
||||||
|
Constraint::Length(3),
|
||||||
|
// Constraint::Length(3),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(size);
|
||||||
|
|
||||||
|
let title = Paragraph::new(
|
||||||
|
app_state
|
||||||
|
.path
|
||||||
|
.clone()
|
||||||
|
.map(|path| path.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or("No file opened".to_owned()),
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::LightCyan))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.border_type(BorderType::Plain),
|
||||||
|
);
|
||||||
|
rect.render_widget(title, chunks[1]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
let cp = Paragraph::new(COPYRIGHT)
|
||||||
|
.style(Style::default().fg(Color::LightCyan))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.title("Copyright")
|
||||||
|
.border_type(BorderType::Plain),
|
||||||
|
);
|
||||||
|
rect.render_widget(cp, chunks[2]);
|
||||||
|
*/
|
||||||
|
|
||||||
|
let (row, column) = app_state.cursor.addr();
|
||||||
|
if row == self.top_row && row >= 1 {
|
||||||
|
self.top_row -= 1;
|
||||||
|
} else if row - self.top_row == (chunks[0].height - 1).into() {
|
||||||
|
self.top_row += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents = Paragraph::new(app_state.contents.contents()).scroll((self.top_row as u16, 0));
|
||||||
|
rect.render_widget(contents, chunks[0]);
|
||||||
|
|
||||||
|
rect.set_cursor(chunks[0].x + column as u16, chunks[0].y + (row - self.top_row) as u16);
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: tui::backend::Backend> Drop for Canvas<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
let _ = self.terminal.show_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue