Contain the app UI inside of a component
This commit is contained in:
parent
8f4d424d1d
commit
54b34d81ec
|
@ -1,10 +1,9 @@
|
|||
mod ui;
|
||||
mod state;
|
||||
mod types;
|
||||
|
||||
use crossterm::{
|
||||
event::{self, KeyCode, KeyEvent, KeyModifiers},
|
||||
terminal::{disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ui::Canvas;
|
||||
use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers};
|
||||
use state::AppState;
|
||||
use std::{
|
||||
env, io,
|
||||
|
@ -12,80 +11,12 @@ use std::{
|
|||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout},
|
||||
style::{Color, Style},
|
||||
widgets::{Block, BorderType, Borders, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
// 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;
|
||||
|
||||
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().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))
|
||||
.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, column) = app_state.cursor.addr();
|
||||
let row = row as u16;
|
||||
let column = column as u16;
|
||||
rect.set_cursor(chunks[1].x + column, chunks[1].y + row);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
|
@ -113,14 +44,14 @@ fn handle_input(tx: mpsc::Sender<Event<KeyEvent>>, tick_rate: Duration) {
|
|||
|
||||
fn app_loop<T>(
|
||||
mut app_state: AppState,
|
||||
terminal: &mut Terminal<T>,
|
||||
mut screen: Canvas<T>,
|
||||
rx: mpsc::Receiver<Event<KeyEvent>>,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
T: tui::backend::Backend,
|
||||
{
|
||||
loop {
|
||||
render(&app_state, terminal)?;
|
||||
screen.render(&app_state)?;
|
||||
|
||||
match rx.recv()? {
|
||||
Event::Input(event)
|
||||
|
@ -152,17 +83,14 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
handle_input(tx, tick_rate);
|
||||
});
|
||||
|
||||
enable_raw_mode()?;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
|
||||
let result = app_loop(app_state, &mut terminal, rx);
|
||||
let screen = Canvas::new(terminal);
|
||||
|
||||
disable_raw_mode()?;
|
||||
terminal.show_cursor()?;
|
||||
let result = app_loop(app_state, screen, rx);
|
||||
|
||||
result?;
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ pub struct Document {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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