Prepare ui structure
This commit is contained in:
parent
9c5f027898
commit
a50ecaee68
4 changed files with 130 additions and 46 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -197,6 +197,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"cove-core",
|
"cove-core",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"futures",
|
||||||
"palette",
|
"palette",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -222,6 +223,7 @@ checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
|
"futures-core",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ edition = "2021"
|
||||||
anyhow = "1.0.53"
|
anyhow = "1.0.53"
|
||||||
clap = { version = "3.1.0", features = ["derive"] }
|
clap = { version = "3.1.0", features = ["derive"] }
|
||||||
cove-core = { path = "../cove-core" }
|
cove-core = { path = "../cove-core" }
|
||||||
crossterm = "0.22.1"
|
crossterm = { version = "0.22.1", features = ["event-stream"] }
|
||||||
# futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
palette = "0.6.0"
|
palette = "0.6.0"
|
||||||
# serde_json = "1.0.78"
|
# serde_json = "1.0.78"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
|
|
||||||
|
|
@ -5,31 +5,15 @@ mod room;
|
||||||
mod textline;
|
mod textline;
|
||||||
mod ui;
|
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::execute;
|
||||||
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
|
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
|
||||||
use tui::backend::CrosstermBackend;
|
use tui::backend::CrosstermBackend;
|
||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
use ui::Ui;
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
|
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
|
// 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!(
|
execute!(
|
||||||
terminal.backend_mut(),
|
terminal.backend_mut(),
|
||||||
LeaveAlternateScreen,
|
LeaveAlternateScreen,
|
||||||
DisableMouseCapture
|
DisableMouseCapture
|
||||||
)?;
|
)?;
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,70 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::Stdout;
|
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::backend::CrosstermBackend;
|
||||||
use tui::widgets::Paragraph;
|
use tui::widgets::Paragraph;
|
||||||
use tui::Terminal;
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
use crate::room::Room;
|
pub type Backend = CrosstermBackend<Stdout>;
|
||||||
|
|
||||||
pub enum Overlay {
|
#[derive(Debug)]
|
||||||
Error(String),
|
pub enum UiEvent {
|
||||||
ChooseRoom(String),
|
Term(Event),
|
||||||
|
Redraw,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventHandleResult {
|
||||||
|
Continue,
|
||||||
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Ui {
|
pub struct Ui {
|
||||||
rooms: HashMap<String, Arc<Mutex<Room>>>,
|
event_tx: UnboundedSender<UiEvent>,
|
||||||
room: Option<Arc<Mutex<Room>>>,
|
rooms_width: i32,
|
||||||
overlay: Option<Overlay>,
|
log: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
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,
|
&mut self,
|
||||||
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
terminal: &mut Terminal<Backend>,
|
||||||
|
event_rx: &mut UnboundedReceiver<UiEvent>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
// 1. Render current state
|
||||||
terminal.autoresize()?;
|
terminal.autoresize()?;
|
||||||
|
|
||||||
let mut frame = terminal.get_frame();
|
let mut frame = terminal.get_frame();
|
||||||
frame.render_widget(Paragraph::new("Hello world!"), frame.size());
|
self.render(&mut frame).await?;
|
||||||
|
|
||||||
// Do a little dance to please the borrow checker
|
// Do a little dance to please the borrow checker
|
||||||
let cursor = frame.cursor();
|
let cursor = frame.cursor();
|
||||||
|
|
@ -40,6 +75,69 @@ impl Ui {
|
||||||
terminal.flush_backend()?;
|
terminal.flush_backend()?;
|
||||||
terminal.swap_buffers();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue