Skip to content

Commit

Permalink
async example
Browse files Browse the repository at this point in the history
  • Loading branch information
Kacperacy committed Apr 28, 2024
1 parent 0da2e7f commit 30a4c5a
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 123 deletions.
68 changes: 25 additions & 43 deletions src/app.rs
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;
}
}
}
28 changes: 0 additions & 28 deletions src/errors.rs

This file was deleted.

83 changes: 83 additions & 0 deletions src/event.rs
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",
)))
}
}
23 changes: 23 additions & 0 deletions src/handler.rs
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(())
}
9 changes: 9 additions & 0 deletions src/lib.rs
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;
74 changes: 22 additions & 52 deletions src/main.rs
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(())
}
53 changes: 53 additions & 0 deletions src/tui.rs
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(())
}
}
29 changes: 29 additions & 0 deletions src/ui.rs
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(),
)
}

0 comments on commit 30a4c5a

Please sign in to comment.