Prepare ui structure

This commit is contained in:
Joscha 2022-02-23 17:53:26 +01:00
parent 9c5f027898
commit a50ecaee68
4 changed files with 130 additions and 46 deletions

2
Cargo.lock generated
View file

@ -197,6 +197,7 @@ dependencies = [
"clap",
"cove-core",
"crossterm",
"futures",
"palette",
"thiserror",
"tokio",
@ -222,6 +223,7 @@ checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
dependencies = [
"bitflags",
"crossterm_winapi",
"futures-core",
"libc",
"mio",
"parking_lot",

View file

@ -7,8 +7,8 @@ edition = "2021"
anyhow = "1.0.53"
clap = { version = "3.1.0", features = ["derive"] }
cove-core = { path = "../cove-core" }
crossterm = "0.22.1"
# futures = "0.3.21"
crossterm = { version = "0.22.1", features = ["event-stream"] }
futures = "0.3.21"
palette = "0.6.0"
# serde_json = "1.0.78"
thiserror = "1.0.30"

View file

@ -5,31 +5,15 @@ mod room;
mod textline;
mod ui;
use std::io::{self, Stdout};
use std::io;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode};
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
use tui::backend::CrosstermBackend;
use tui::Terminal;
use ui::Ui;
async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> anyhow::Result<()> {
let mut ui = Ui::default();
loop {
ui.render_to_terminal(terminal).await?;
let event = crossterm::event::read()?;
if let Event::Key(k) = event {
if k.code == KeyCode::Esc {
break;
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
@ -42,14 +26,14 @@ async fn main() -> anyhow::Result<()> {
)?;
// Defer error handling so the terminal always gets restored properly
let result = run(&mut terminal).await;
let result = Ui::run(&mut terminal).await;
crossterm::terminal::disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
crossterm::terminal::disable_raw_mode()?;
result?;

View file

@ -1,45 +1,143 @@
use std::collections::HashMap;
use std::io::Stdout;
use std::sync::Arc;
use tokio::sync::Mutex;
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, MouseEvent};
use futures::StreamExt;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tui::backend::CrosstermBackend;
use tui::widgets::Paragraph;
use tui::Terminal;
use tui::{Frame, Terminal};
use crate::room::Room;
pub type Backend = CrosstermBackend<Stdout>;
pub enum Overlay {
Error(String),
ChooseRoom(String),
#[derive(Debug)]
pub enum UiEvent {
Term(Event),
Redraw,
}
enum EventHandleResult {
Continue,
Stop,
}
#[derive(Default)]
pub struct Ui {
rooms: HashMap<String, Arc<Mutex<Room>>>,
room: Option<Arc<Mutex<Room>>>,
overlay: Option<Overlay>,
event_tx: UnboundedSender<UiEvent>,
rooms_width: i32,
log: Vec<String>,
}
impl Ui {
pub async fn render_to_terminal(
fn new(event_tx: UnboundedSender<UiEvent>) -> Self {
Self {
event_tx,
rooms_width: 24,
log: vec!["Hello world!".to_string()],
}
}
pub async fn run(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
let (event_tx, mut event_rx) = mpsc::unbounded_channel();
let mut ui = Self::new(event_tx.clone());
tokio::select! {
e = ui.run_main(terminal, &mut event_rx) => e,
e = Self::shovel_crossterm_events(event_tx) => e,
}
}
async fn shovel_crossterm_events(tx: UnboundedSender<UiEvent>) -> anyhow::Result<()> {
// Implemented manually because UnboundedSender doesn't implement the Sink trait
let mut stream = EventStream::new();
while let Some(event) = stream.next().await {
tx.send(UiEvent::Term(event?))?;
}
Ok(())
}
async fn run_main(
&mut self,
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
terminal: &mut Terminal<Backend>,
event_rx: &mut UnboundedReceiver<UiEvent>,
) -> anyhow::Result<()> {
terminal.autoresize()?;
loop {
// 1. Render current state
terminal.autoresize()?;
let mut frame = terminal.get_frame();
frame.render_widget(Paragraph::new("Hello world!"), frame.size());
let mut frame = terminal.get_frame();
self.render(&mut frame).await?;
// Do a little dance to please the borrow checker
let cursor = frame.cursor();
drop(frame);
terminal.set_cursor_opt(cursor)?;
// Do a little dance to please the borrow checker
let cursor = frame.cursor();
drop(frame);
terminal.set_cursor_opt(cursor)?;
terminal.flush()?;
terminal.flush_backend()?;
terminal.swap_buffers();
terminal.flush()?;
terminal.flush_backend()?;
terminal.swap_buffers();
// 2. Handle events
let event = event_rx.recv().await;
self.log.push(format!("{event:?}"));
let result = match event {
Some(UiEvent::Term(Event::Key(event))) => self.handle_key_event(event).await?,
Some(UiEvent::Term(Event::Mouse(event))) => self.handle_mouse_event(event).await?,
Some(UiEvent::Term(Event::Resize(_, _))) => EventHandleResult::Continue,
Some(UiEvent::Redraw) => EventHandleResult::Continue,
None => EventHandleResult::Stop,
};
match result {
EventHandleResult::Continue => {}
EventHandleResult::Stop => break Ok(()),
}
}
}
async fn handle_key_event(&mut self, event: KeyEvent) -> anyhow::Result<EventHandleResult> {
Ok(match event.code {
// KeyCode::Backspace => todo!(),
// KeyCode::Enter => todo!(),
// KeyCode::Left => todo!(),
// KeyCode::Right => todo!(),
// KeyCode::Up => todo!(),
// KeyCode::Down => todo!(),
// KeyCode::Home => todo!(),
// KeyCode::End => todo!(),
// KeyCode::PageUp => todo!(),
// KeyCode::PageDown => todo!(),
// KeyCode::Tab => todo!(),
// KeyCode::BackTab => todo!(),
// KeyCode::Delete => todo!(),
// KeyCode::Insert => todo!(),
// KeyCode::F(_) => todo!(),
// KeyCode::Char(_) => todo!(),
// KeyCode::Null => todo!(),
KeyCode::Esc => EventHandleResult::Stop,
_ => EventHandleResult::Continue,
})
}
async fn handle_mouse_event(&mut self, event: MouseEvent) -> anyhow::Result<EventHandleResult> {
Ok(match event.kind {
// MouseEventKind::Down(_) => todo!(),
// MouseEventKind::Up(_) => todo!(),
// MouseEventKind::Drag(_) => todo!(),
// MouseEventKind::Moved => todo!(),
// MouseEventKind::ScrollDown => todo!(),
// MouseEventKind::ScrollUp => todo!(),
_ => EventHandleResult::Continue,
})
}
async fn render(&mut self, frame: &mut Frame<'_, Backend>) -> anyhow::Result<()> {
let scroll = if self.log.len() as u16 > frame.size().height {
self.log.len() as u16 - frame.size().height
} else {
0
};
frame.render_widget(
Paragraph::new(self.log.join("\n")).scroll((scroll, 0)),
frame.size(),
);
Ok(())
}
}