tui client wip
This commit is contained in:
parent
3f5fbf4302
commit
a73528bfb6
161
Cargo.lock
generated
161
Cargo.lock
generated
|
|
@ -1147,6 +1147,12 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.83"
|
version = "1.0.83"
|
||||||
|
|
@ -1230,6 +1236,18 @@ dependencies = [
|
||||||
"libloading 0.7.4",
|
"libloading 0.7.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "client_tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
|
"crossterm",
|
||||||
|
"ratatui",
|
||||||
|
"renet",
|
||||||
|
"store",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
@ -1424,6 +1442,31 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -1470,6 +1513,12 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encase"
|
name = "encase"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
@ -1900,6 +1949,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -1962,6 +2017,12 @@ dependencies = [
|
||||||
"hashbrown 0.14.2",
|
"hashbrown 0.14.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inflections"
|
name = "inflections"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
|
@ -2030,6 +2091,15 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
|
|
@ -2209,6 +2279,15 @@ version = "0.4.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1efa59af2ddfad1854ae27d75009d538d0998b4b2fd47083e743ac1a10e46c60"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mach2"
|
name = "mach2"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -2825,6 +2904,24 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
|
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"cassowary",
|
||||||
|
"crossterm",
|
||||||
|
"indoc",
|
||||||
|
"itertools",
|
||||||
|
"lru",
|
||||||
|
"paste",
|
||||||
|
"strum",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -2975,6 +3072,12 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruzstd"
|
name = "ruzstd"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
@ -3053,6 +3156,36 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
|
@ -3120,6 +3253,28 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.25.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 2.0.38",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
@ -3395,6 +3550,12 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ resolver="2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"client",
|
"client",
|
||||||
|
"client_tui",
|
||||||
"server",
|
"server",
|
||||||
"store"
|
"store"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
4
Makefile
4
Makefile
|
|
@ -6,4 +6,6 @@ startserver:
|
||||||
startclient1:
|
startclient1:
|
||||||
RUST_LOG=trictrac_client cargo run --bin=trictrac-client Titi
|
RUST_LOG=trictrac_client cargo run --bin=trictrac-client Titi
|
||||||
startclient2:
|
startclient2:
|
||||||
RUST_LOG=trictrac_client cargo run --bin=trictrac-client Tutu
|
RUST_LOG=trictrac_client cargo run --bin=trictrac-client Titu
|
||||||
|
startclienttui:
|
||||||
|
RUST_LOG=trictrac_client cargo run --bin=client_tui Tutu
|
||||||
|
|
|
||||||
53
client_tui/src/app.rs
Normal file
53
client_tui/src/app.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Application.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct App {
|
||||||
|
// should the application exit?
|
||||||
|
pub should_quit: bool,
|
||||||
|
// counter
|
||||||
|
pub counter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
// Constructs a new instance of [`App`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the tick event of the terminal.
|
||||||
|
pub fn tick(&self) {}
|
||||||
|
|
||||||
|
// Set running to false to quit the application.
|
||||||
|
pub fn quit(&mut self) {
|
||||||
|
self.should_quit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_counter(&mut self) {
|
||||||
|
if let Some(res) = self.counter.checked_add(1) {
|
||||||
|
self.counter = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrement_counter(&mut self) {
|
||||||
|
if let Some(res) = self.counter.checked_sub(1) {
|
||||||
|
self.counter = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_app_increment_counter() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.increment_counter();
|
||||||
|
assert_eq!(app.counter, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_decrement_counter() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.decrement_counter();
|
||||||
|
assert_eq!(app.counter, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
87
client_tui/src/event.rs
Normal file
87
client_tui/src/event.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::{
|
||||||
|
sync::mpsc,
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||||
|
|
||||||
|
// Terminal events.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
// Terminal tick.
|
||||||
|
Tick,
|
||||||
|
// Key press.
|
||||||
|
Key(KeyEvent),
|
||||||
|
// Mouse click/scroll.
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
// Terminal resize.
|
||||||
|
Resize(u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal event handler.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EventHandler {
|
||||||
|
// Event sender channel.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
sender: mpsc::Sender<Event>,
|
||||||
|
// Event receiver channel.
|
||||||
|
receiver: mpsc::Receiver<Event>,
|
||||||
|
// Event handler thread.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
handler: thread::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventHandler {
|
||||||
|
// Constructs a new instance of [`EventHandler`].
|
||||||
|
pub fn new(tick_rate: u64) -> Self {
|
||||||
|
let tick_rate = Duration::from_millis(tick_rate);
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let handler = {
|
||||||
|
let sender = sender.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
let timeout = tick_rate
|
||||||
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or(tick_rate);
|
||||||
|
|
||||||
|
if event::poll(timeout).expect("no events available") {
|
||||||
|
match event::read().expect("unable to read event") {
|
||||||
|
CrosstermEvent::Key(e) => {
|
||||||
|
if e.kind == event::KeyEventKind::Press {
|
||||||
|
sender.send(Event::Key(e))
|
||||||
|
} else {
|
||||||
|
Ok(()) // ignore KeyEventKind::Release on windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
|
||||||
|
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
.expect("failed to send terminal event")
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_tick.elapsed() >= tick_rate {
|
||||||
|
sender.send(Event::Tick).expect("failed to send tick event");
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive the next event from the handler thread.
|
||||||
|
//
|
||||||
|
// This function will always block the current thread if
|
||||||
|
// there is no data available and it's possible for more data to be sent.
|
||||||
|
pub fn next(&self) -> Result<Event> {
|
||||||
|
Ok(self.receiver.recv()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,41 +1,50 @@
|
||||||
use crossterm::{
|
// Application.
|
||||||
event::{self, KeyCode, KeyEventKind},
|
pub mod app;
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
||||||
ExecutableCommand,
|
// Terminal events handler.
|
||||||
};
|
pub mod event;
|
||||||
use ratatui::{
|
|
||||||
prelude::{CrosstermBackend, Stylize, Terminal},
|
// Widget renderer.
|
||||||
widgets::Paragraph,
|
pub mod ui;
|
||||||
};
|
|
||||||
use std::io::{stdout, Result};
|
// Terminal user interface.
|
||||||
|
pub mod tui;
|
||||||
|
|
||||||
|
// Application updater.
|
||||||
|
pub mod update;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use app::App;
|
||||||
|
use event::{Event, EventHandler};
|
||||||
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
|
use tui::Tui;
|
||||||
|
use update::update;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
// Create an application.
|
||||||
enable_raw_mode()?;
|
let mut app = App::new();
|
||||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
|
||||||
terminal.clear()?;
|
|
||||||
|
|
||||||
loop {
|
// Initialize the terminal user interface.
|
||||||
terminal.draw(|frame| {
|
let backend = CrosstermBackend::new(std::io::stderr());
|
||||||
let area = frame.size();
|
let terminal = Terminal::new(backend)?;
|
||||||
frame.render_widget(
|
let events = EventHandler::new(250);
|
||||||
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
|
let mut tui = Tui::new(terminal, events);
|
||||||
.white()
|
tui.enter()?;
|
||||||
.on_blue(),
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if event::poll(std::time::Duration::from_millis(16))? {
|
// Start the main loop.
|
||||||
if let event::Event::Key(key) = event::read()? {
|
while !app.should_quit {
|
||||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
// Render the user interface.
|
||||||
break;
|
tui.draw(&mut app)?;
|
||||||
}
|
// Handle events.
|
||||||
}
|
match tui.events.next()? {
|
||||||
}
|
Event::Tick => {}
|
||||||
|
Event::Key(key_event) => update(&mut app, key_event),
|
||||||
|
Event::Mouse(_) => {}
|
||||||
|
Event::Resize(_, _) => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
// Exit the user interface.
|
||||||
disable_raw_mode()?;
|
tui.exit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
client_tui/src/tui.rs
Normal file
77
client_tui/src/tui.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use std::{io, panic};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::{
|
||||||
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
|
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type CrosstermTerminal = ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stderr>>;
|
||||||
|
|
||||||
|
use crate::{app::App, event::EventHandler, ui};
|
||||||
|
|
||||||
|
// Representation of a terminal user interface.
|
||||||
|
//
|
||||||
|
// It is responsible for setting up the terminal,
|
||||||
|
// initializing the interface and handling the draw events.
|
||||||
|
pub struct Tui {
|
||||||
|
// Interface to the Terminal.
|
||||||
|
terminal: CrosstermTerminal,
|
||||||
|
// Terminal event handler.
|
||||||
|
pub events: EventHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
// Constructs a new instance of [`Tui`].
|
||||||
|
pub fn new(terminal: CrosstermTerminal, events: EventHandler) -> Self {
|
||||||
|
Self { terminal, events }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes the terminal interface.
|
||||||
|
//
|
||||||
|
// It enables the raw mode and sets terminal properties.
|
||||||
|
pub fn enter(&mut self) -> Result<()> {
|
||||||
|
terminal::enable_raw_mode()?;
|
||||||
|
crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
|
||||||
|
// Define a custom panic hook to reset the terminal properties.
|
||||||
|
// This way, you won't have your terminal messed up if an unexpected error happens.
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// [`Draw`] the terminal interface by [`rendering`] the widgets.
|
||||||
|
//
|
||||||
|
// [`Draw`]: tui::Terminal::draw
|
||||||
|
// [`rendering`]: crate::ui:render
|
||||||
|
pub fn draw(&mut self, app: &mut App) -> Result<()> {
|
||||||
|
self.terminal.draw(|frame| ui::render(app, frame))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets the terminal interface.
|
||||||
|
//
|
||||||
|
// This function is also used for the panic hook to revert
|
||||||
|
// the terminal properties if unexpected errors occur.
|
||||||
|
fn reset() -> Result<()> {
|
||||||
|
terminal::disable_raw_mode()?;
|
||||||
|
crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exits the terminal interface.
|
||||||
|
//
|
||||||
|
// It disables the raw mode and reverts back the terminal properties.
|
||||||
|
pub fn exit(&mut self) -> Result<()> {
|
||||||
|
Self::reset()?;
|
||||||
|
self.terminal.show_cursor()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
30
client_tui/src/ui.rs
Normal file
30
client_tui/src/ui.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
use ratatui::{
|
||||||
|
prelude::{Alignment, Frame},
|
||||||
|
style::{Color, Style},
|
||||||
|
widgets::{Block, BorderType, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
pub fn render(app: &mut App, f: &mut Frame) {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(format!(
|
||||||
|
"
|
||||||
|
Press `Esc`, `Ctrl-C` or `q` to stop running.\n\
|
||||||
|
Press `j` and `k` to increment and decrement the counter respectively.\n\
|
||||||
|
Counter: {}
|
||||||
|
",
|
||||||
|
app.counter
|
||||||
|
))
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Counter App")
|
||||||
|
.title_alignment(Alignment::Center)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded),
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::Yellow))
|
||||||
|
.alignment(Alignment::Center),
|
||||||
|
f.size(),
|
||||||
|
)
|
||||||
|
}
|
||||||
17
client_tui/src/update.rs
Normal file
17
client_tui/src/update.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
pub fn update(app: &mut App, key_event: KeyEvent) {
|
||||||
|
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 | KeyCode::Char('j') => app.increment_counter(),
|
||||||
|
KeyCode::Left | KeyCode::Char('k') => app.decrement_counter(),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue