-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
244 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,42 @@ | ||
use std::collections::HashMap; | ||
use std::error; | ||
|
||
use serde_json::Result; | ||
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>; | ||
|
||
pub enum CurrentScreen { | ||
Main, | ||
Editing, | ||
Exiting, | ||
} | ||
|
||
pub enum CurrentlyEditing { | ||
Key, | ||
Value, | ||
#[derive(Debug)] | ||
pub struct App { | ||
pub running: bool, | ||
pub counter: u8, | ||
} | ||
|
||
pub struct App { | ||
pub key_input: String, | ||
pub value_input: String, | ||
pub pairs: HashMap<String, String>, | ||
pub current_screen: CurrentScreen, | ||
pub currently_editing: Option<CurrentlyEditing>, | ||
impl Default for App { | ||
fn default() -> Self { | ||
Self { | ||
running: true, | ||
counter: 0, | ||
} | ||
} | ||
} | ||
|
||
impl App { | ||
pub fn new() -> App { | ||
App { | ||
key_input: String::new(), | ||
value_input: String::new(), | ||
pairs: HashMap::new(), | ||
current_screen: CurrentScreen::Main, | ||
currently_editing: None, | ||
} | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
pub fn save_key_value(&mut self) { | ||
self.pairs | ||
.insert(self.key_input.clone(), self.value_input.clone()); | ||
pub fn tick(&self) {} | ||
|
||
self.key_input = String::new(); | ||
self.value_input = String::new(); | ||
self.currently_editing = None; | ||
pub fn quit(&mut self) { | ||
self.running = false; | ||
} | ||
|
||
pub fn toggle_edititng(&mut self) { | ||
if let Some(edit_mode) = &self.currently_editing { | ||
match edit_mode { | ||
CurrentlyEditing::Key => self.currently_editing = Some(CurrentlyEditing::Value), | ||
CurrentlyEditing::Value => self.currently_editing = Some(CurrentlyEditing::Key), | ||
}; | ||
} else { | ||
self.currently_editing = Some(CurrentlyEditing::Key); | ||
pub fn increment_counter(&mut self) { | ||
if let Some(res) = self.counter.checked_add(1) { | ||
self.counter = res; | ||
} | ||
} | ||
|
||
pub fn print_json(&self) -> Result<()> { | ||
let output = serde_json::to_string(&self.pairs)?; | ||
println!("{}", output); | ||
Ok(()) | ||
pub fn decrement_counter(&mut self) { | ||
if let Some(res) = self.counter.checked_sub(1) { | ||
self.counter = res; | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use std::time::Duration; | ||
|
||
use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent}; | ||
use futures::{FutureExt, StreamExt}; | ||
use tokio::sync::mpsc; | ||
|
||
use crate::app::AppResult; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub enum Event { | ||
Tick, | ||
Key(KeyEvent), | ||
Mouse(MouseEvent), | ||
Resize(u16, u16), | ||
} | ||
|
||
#[allow(dead_code)] | ||
#[derive(Debug)] | ||
pub struct EventHandler { | ||
sender: mpsc::UnboundedSender<Event>, | ||
receiver: mpsc::UnboundedReceiver<Event>, | ||
handler: tokio::task::JoinHandle<()>, | ||
} | ||
|
||
impl EventHandler { | ||
pub fn new(tick_rate: u64) -> Self { | ||
let tick_rate = Duration::from_millis(tick_rate); | ||
let (sender, receiver) = mpsc::unbounded_channel(); | ||
let _sender = sender.clone(); | ||
let handler = tokio::spawn(async move { | ||
let mut reader = crossterm::event::EventStream::new(); | ||
let mut tick = tokio::time::interval(tick_rate); | ||
loop { | ||
let tick_delay = tick.tick(); | ||
let crossterm_event = reader.next().fuse(); | ||
tokio::select! { | ||
_ = _sender.closed() => { | ||
break; | ||
} | ||
_ = tick_delay => { | ||
_sender.send(Event::Tick).unwrap(); | ||
} | ||
Some(Ok(evt)) = crossterm_event => { | ||
match evt { | ||
CrosstermEvent::Key(key) => { | ||
if key.kind == crossterm::event::KeyEventKind::Press { | ||
_sender.send(Event::Key(key)).unwrap(); | ||
} | ||
}, | ||
CrosstermEvent::Mouse(mouse) => { | ||
_sender.send(Event::Mouse(mouse)).unwrap(); | ||
}, | ||
CrosstermEvent::Resize(x, y) => { | ||
_sender.send(Event::Resize(x, y)).unwrap(); | ||
}, | ||
CrosstermEvent::FocusLost => { | ||
}, | ||
CrosstermEvent::FocusGained => { | ||
}, | ||
CrosstermEvent::Paste(_) => { | ||
}, | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
Self { | ||
sender, | ||
receiver, | ||
handler, | ||
} | ||
} | ||
|
||
pub async fn next(&mut self) -> AppResult<Event> { | ||
self.receiver | ||
.recv() | ||
.await | ||
.ok_or(Box::new(std::io::Error::new( | ||
std::io::ErrorKind::Other, | ||
"This is an IO error", | ||
))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use crate::app::{App, AppResult}; | ||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; | ||
|
||
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | ||
match key_event.code { | ||
KeyCode::Esc | KeyCode::Char('q') => { | ||
app.quit(); | ||
} | ||
KeyCode::Char('c') | KeyCode::Char('C') => { | ||
if key_event.modifiers == KeyModifiers::CONTROL { | ||
app.quit(); | ||
} | ||
} | ||
KeyCode::Right => { | ||
app.increment_counter(); | ||
} | ||
KeyCode::Left => { | ||
app.decrement_counter(); | ||
} | ||
_ => {} | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
pub mod app; | ||
|
||
pub mod event; | ||
|
||
pub mod ui; | ||
|
||
pub mod tui; | ||
|
||
pub mod handler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,30 @@ | ||
use ratatui::{backend::CrosstermBackend, Terminal}; | ||
|
||
mod tui; | ||
use rust_edit::app::{App, AppResult}; | ||
use rust_edit::event::{Event, EventHandler}; | ||
use rust_edit::handler::handle_key_events; | ||
use rust_edit::tui::Tui; | ||
|
||
pub enum Event { | ||
Quit, | ||
Error, | ||
Tick, | ||
Render, | ||
Key(KeyEvent), | ||
} | ||
|
||
fn update(app: &mut App, event: Event) -> Result<()> { | ||
if let Event::Key(key) = event { | ||
match key.code { | ||
Char('j') => app.counter += 1, | ||
Char('k') => app.counter -= 1, | ||
Char('q') => app.should_quit = true, | ||
_ => {} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
async fn run() => Result<()> { | ||
let mut events = tui::EventHandler::new(); | ||
|
||
let mut t = Terminal::new(CrosstermBackend::new(std::io::stderr()))?; | ||
|
||
let mut app = App { counter: 0, should_quit: false}; | ||
|
||
loop { | ||
let event = events.next().await?; | ||
|
||
update(&mut app, event)?; | ||
|
||
t.draw(|f| { | ||
ui(f, &app); | ||
})?; | ||
|
||
if app.should_quit { | ||
break; | ||
#[tokio::main] | ||
async fn main() -> AppResult<()> { | ||
let mut app = App::new(); | ||
|
||
let backend = CrosstermBackend::new(std::io::stderr()); | ||
let terminal = Terminal::new(backend)?; | ||
let events = EventHandler::new(250); | ||
let mut tui = Tui::new(terminal, events); | ||
tui.init()?; | ||
|
||
while app.running { | ||
tui.draw(&mut app)?; | ||
match tui.events.next().await? { | ||
Event::Tick => app.tick(), | ||
Event::Key(key_event) => handle_key_events(key_event, &mut app)?, | ||
Event::Mouse(_) => {} | ||
Event::Resize(_, _) => {} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<()> { | ||
startup()?; | ||
|
||
let result = run(); | ||
|
||
shutdown()?; | ||
|
||
result?; | ||
|
||
tui.exit()?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use crate::app::{App, AppResult}; | ||
use crate::event::EventHandler; | ||
use crate::ui; | ||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; | ||
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; | ||
use ratatui::backend::Backend; | ||
use ratatui::Terminal; | ||
use std::io; | ||
use std::panic; | ||
|
||
#[derive(Debug)] | ||
pub struct Tui<B: Backend> { | ||
terminal: Terminal<B>, | ||
pub events: EventHandler, | ||
} | ||
|
||
impl<B: Backend> Tui<B> { | ||
pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self { | ||
Self { terminal, events } | ||
} | ||
|
||
pub fn init(&mut self) -> AppResult<()> { | ||
terminal::enable_raw_mode()?; | ||
crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?; | ||
|
||
let panic_hook = panic::take_hook(); | ||
panic::set_hook(Box::new(move |panic| { | ||
Self::reset().expect("failed to reset the terminal"); | ||
panic_hook(panic); | ||
})); | ||
|
||
self.terminal.hide_cursor()?; | ||
self.terminal.clear()?; | ||
Ok(()) | ||
} | ||
|
||
pub fn draw(&mut self, app: &mut App) -> AppResult<()> { | ||
self.terminal.draw(|frame| ui::render(app, frame))?; | ||
Ok(()) | ||
} | ||
|
||
fn reset() -> AppResult<()> { | ||
terminal::disable_raw_mode()?; | ||
crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?; | ||
Ok(()) | ||
} | ||
|
||
pub fn exit(&mut self) -> AppResult<()> { | ||
Self::reset()?; | ||
self.terminal.show_cursor()?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use ratatui::{ | ||
layout::Alignment, | ||
style::{Color, Style}, | ||
widgets::{Block, BorderType, Paragraph}, | ||
Frame, | ||
}; | ||
|
||
use crate::app::App; | ||
|
||
pub fn render(app: &mut App, frame: &mut Frame) { | ||
frame.render_widget( | ||
Paragraph::new(format!( | ||
"This is a tui template.\n\ | ||
Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ | ||
Press left and right to increment and decrement the counter respectively.\n\ | ||
Counter: {}", | ||
app.counter | ||
)) | ||
.block( | ||
Block::bordered() | ||
.title("Template") | ||
.title_alignment(Alignment::Center) | ||
.border_type(BorderType::Rounded), | ||
) | ||
.style(Style::default().fg(Color::Cyan).bg(Color::Black)) | ||
.centered(), | ||
frame.size(), | ||
) | ||
} |