Eiyeron Fulmincendii пре 2 година
комит
dbb36c7170
11 измењених фајлова са 1037 додато и 0 уклоњено
  1. 1 0
      .gitignore
  2. 469 0
      Cargo.lock
  3. 11 0
      Cargo.toml
  4. 27 0
      LICENSE
  5. 90 0
      src/app.rs
  6. 78 0
      src/event.rs
  7. 26 0
      src/handler.rs
  8. 133 0
      src/lumps.rs
  9. 65 0
      src/main.rs
  10. 57 0
      src/tui.rs
  11. 80 0
      src/ui.rs

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/target

+ 469 - 0
Cargo.lock

@@ -0,0 +1,469 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
+dependencies = [
+ "bitflags",
+ "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]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.146"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
+
+[[package]]
+name = "lock_api"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+
+[[package]]
+name = "mio"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ratatui"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce841e0486e7c2412c3740168ede33adeba8e154a15107b879d8162d77c7174e"
+dependencies = [
+ "bitflags",
+ "cassowary",
+ "crossterm",
+ "time",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.164"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
+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]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
+dependencies = [
+ "clap",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "0.15.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "time"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
+dependencies = [
+ "libc",
+ "num_threads",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "wad"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cca16874efe33ab5a2414731b43cfee9897aaf43e2824718c7b2564ee3e1aa9d"
+dependencies = [
+ "byteorder",
+ "structopt",
+]
+
+[[package]]
+name = "wad-googles"
+version = "0.1.0"
+dependencies = [
+ "crossterm",
+ "ratatui",
+ "wad",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

+ 11 - 0
Cargo.toml

@@ -0,0 +1,11 @@
+[package]
+name = "wad-googles"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+crossterm = "0.26.1"
+ratatui = { version = "0.21.0", features = ["all-widgets"]}
+wad = "0.3.2"

+ 27 - 0
LICENSE

@@ -0,0 +1,27 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 Eiyeron
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 90 - 0
src/app.rs

@@ -0,0 +1,90 @@
+use std::error;
+use ratatui::widgets::ListState;
+use wad::{Wad, load_wad_file};
+
+use crate::lumps::{LumpInfo, get_lumps};
+
+/// Application result type.
+pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
+
+/// Application.
+
+pub struct StatefulList<T> {
+    pub state: ListState,
+    pub items: Vec<T>,
+}
+
+impl<T> StatefulList<T> {
+    fn with_items(items: Vec<T>) -> StatefulList<T> {
+        StatefulList {
+            state: ListState::default(),
+            items,
+        }
+    }
+
+    pub fn next(&mut self) {
+        let i = match self.state.selected() {
+            Some(i) => {
+                if i >= self.items.len() - 1 {
+                    0
+                } else {
+                    i + 1
+                }
+            }
+            None => 0,
+        };
+        self.state.select(Some(i));
+    }
+
+    pub fn previous(&mut self) {
+        let i = match self.state.selected() {
+            Some(i) => {
+                if i == 0 {
+                    self.items.len() - 1
+                } else {
+                    i - 1
+                }
+            }
+            None => 0,
+        };
+        self.state.select(Some(i));
+    }
+
+    pub fn unselect(&mut self) {
+        self.state.select(None);
+    }
+}
+
+pub struct App {
+    pub wad:Wad,
+    pub lumps:StatefulList<LumpInfo>,
+    pub running:bool,
+}
+
+impl App {
+    fn from_wad(wad_path:&str) -> Self {
+        let wad = load_wad_file(wad_path).unwrap();
+        let lumps = get_lumps(&wad);
+        Self {
+            wad: wad,
+            lumps: StatefulList::with_items(lumps),
+            running: true
+        }
+    }
+}
+
+impl App {
+    /// Constructs a new instance of [`App`].
+    pub fn new(wad_path:&str) -> Self {
+        Self::from_wad(wad_path)
+    }
+
+    /// 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.running = false;
+    }
+
+}

+ 78 - 0
src/event.rs

@@ -0,0 +1,78 @@
+use crate::app::AppResult;
+use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
+use std::sync::mpsc;
+use std::thread;
+use std::time::{Duration, Instant};
+
+/// 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.
+#[allow(dead_code)]
+#[derive(Debug)]
+pub struct EventHandler {
+    /// Event sender channel.
+    sender: mpsc::Sender<Event>,
+    /// Event receiver channel.
+    receiver: mpsc::Receiver<Event>,
+    /// Event handler thread.
+    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) => sender.send(Event::Key(e)),
+                            CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
+                            CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
+                            CrosstermEvent::FocusGained | CrosstermEvent::FocusLost => Ok(()),
+                            _ => 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) -> AppResult<Event> {
+        Ok(self.receiver.recv()?)
+    }
+}

+ 26 - 0
src/handler.rs

@@ -0,0 +1,26 @@
+use crate::app::{App, AppResult};
+use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
+
+/// Handles the key events and updates the state of [`App`].
+pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
+    match key_event.code {
+        // Exit application on `ESC` or `q`
+        KeyCode::Esc | KeyCode::Char('q') => {
+            app.quit();
+        }
+        // Exit application on `Ctrl-C`
+        KeyCode::Char('c') | KeyCode::Char('C') => {
+            if key_event.modifiers == KeyModifiers::CONTROL {
+                app.quit();
+            }
+        }
+        KeyCode::Down => {
+            app.lumps.next();
+        }
+        KeyCode::Up => {
+            app.lumps.previous();
+        }
+        _ => {}
+    }
+    Ok(())
+}

+ 133 - 0
src/lumps.rs

@@ -0,0 +1,133 @@
+use std::marker;
+
+use ratatui::symbols::Marker;
+use wad::Wad;
+
+#[derive(PartialEq, Clone)]
+pub enum MarkerType {
+    Flat,
+    Sprite,
+    Texture,
+    Sound,
+    Map,
+    Unknown,
+}
+
+#[derive(PartialEq)]
+pub enum FileType {
+    Map,
+    Things,
+    Linedefs,
+    Sidedefs,
+    Vertexes,
+    Segs,
+    Ssectors,
+    Nodes,
+    Sectors,
+    Reject,
+    Blockmap,
+    Behavior,
+    //
+    Flat,
+    Sprite,
+    Texture,
+    Sfx,
+}
+
+#[derive(PartialEq)]
+pub enum MarkerBoundary {
+    Single,
+    Begin,
+    End,
+    Unknown,
+}
+
+pub enum LumpType {
+    Marker(MarkerType, MarkerBoundary),
+    File(FileType),
+}
+
+pub struct LumpInfo {
+    pub name: String,
+    pub size: usize,
+    pub lump_type: Option<LumpType>,
+}
+
+fn determine_lump_type(
+    current_group: &Option<MarkerType>,
+    name: &str,
+    size: usize,
+) -> Option<LumpType> {
+    // TODO Make sure that the map lumps are following a map?
+    if size == 0 {
+        match name {
+            "F_START" => return Some(LumpType::Marker(MarkerType::Flat, MarkerBoundary::Begin)),
+            "F_END" => return Some(LumpType::Marker(MarkerType::Flat, MarkerBoundary::End)),
+            "S_START" => return Some(LumpType::Marker(MarkerType::Sprite, MarkerBoundary::Begin)),
+            "S_END" => return Some(LumpType::Marker(MarkerType::Sprite, MarkerBoundary::End)),
+            "TX_START" => return Some(LumpType::Marker(MarkerType::Texture, MarkerBoundary::Begin)),
+            "TX_END" => return Some(LumpType::Marker(MarkerType::Texture, MarkerBoundary::End)),
+            _ => {}
+        }
+    }
+    match name {
+        "THINGS" => return Some(LumpType::File(FileType::Things)),
+        "LINEDEFS" => return Some(LumpType::File(FileType::Linedefs)),
+        "SIDEDEFS" => return Some(LumpType::File(FileType::Sidedefs)),
+        "VERTEXES" => return Some(LumpType::File(FileType::Vertexes)),
+        "SEGS" => return Some(LumpType::File(FileType::Segs)),
+        "SSECTORS" => return Some(LumpType::File(FileType::Ssectors)),
+        "NODES" => return Some(LumpType::File(FileType::Nodes)),
+        "SECTORS" => return Some(LumpType::File(FileType::Sectors)),
+        "REJECT" => return Some(LumpType::File(FileType::Reject)),
+        "BLOCKMAP" => return Some(LumpType::File(FileType::Blockmap)),
+        "BEHAVIOR" => return Some(LumpType::File(FileType::Behavior)),
+        _ => {}
+    };
+    if let Some(marker_type) = &current_group {
+        match marker_type {
+            MarkerType::Flat => return Some(LumpType::File(FileType::Flat)),
+            MarkerType::Sprite => return Some(LumpType::File(FileType::Sprite)),
+            MarkerType::Texture => return Some(LumpType::File(FileType::Texture)),
+            _ => {},
+        }
+    }
+
+    if name.starts_with("MAP") && name.len() == 5 {
+        return Some(LumpType::File(FileType::Map));
+    }
+    None
+}
+
+pub fn get_lumps(wad: &Wad) -> Vec<LumpInfo> {
+    let mut current_marker_group: Option<MarkerType> = None;
+    wad.as_slice()
+        .entry_iter()
+        .map(|entry| {
+            let lump_type = determine_lump_type(&current_marker_group, entry.display_name(), entry.lump.len());
+
+            // Update the iterator state by checking the current lump's type if it's a marker.
+            // Open/close marker groups depending on its type.
+            match &lump_type {
+                Some(LumpType::Marker(marker_type, boundary)) => {
+                    if *boundary == MarkerBoundary::End {
+                        current_marker_group = None;
+                    } else {
+                        match marker_type {
+                            MarkerType::Flat | MarkerType::Sprite | MarkerType::Texture => current_marker_group = Some(marker_type.clone()),
+                            _ => todo!(),
+                        }
+                    }
+                },
+                Some(LumpType::File(FileType::Map)) => current_marker_group = Some(MarkerType::Map),
+                _ => {}
+            };
+
+            LumpInfo {
+                name: entry.display_name().to_string(),
+                size: entry.lump.len(),
+                lump_type: lump_type,
+            }
+        })
+        .collect()
+}

+ 65 - 0
src/main.rs

@@ -0,0 +1,65 @@
+mod app;
+mod lumps;
+mod event;
+mod handler;
+mod tui;
+mod ui;
+
+use crate::app::{App, AppResult};
+use crate::event::{Event, EventHandler};
+use crate::handler::handle_key_events;
+use crate::tui::Tui;
+use ratatui::backend::CrosstermBackend;
+use ratatui::Terminal;
+use std::env;
+use std::io;
+
+fn get_wad_from_args() -> Result<String, &'static str> {
+    let mut args: Vec<String> = env::args().collect();
+
+    if args.len() != 2 {
+        return Err("Too many arguments");
+    }
+    if let Some(path) = args.pop() {
+        return Ok(path);
+    }
+    return Err("Couldn't get the argument");
+}
+
+fn main() -> AppResult<()> {
+    let file_path = match get_wad_from_args() {
+        Ok(str) => str,
+        Err(err) => {
+            eprintln!("{}", err);
+            return Ok(());
+        }
+    };
+
+    // Create an application.
+    let mut app = App::new(&file_path);
+
+
+    // Initialize the terminal user interface.
+    let backend = CrosstermBackend::new(io::stderr());
+    let terminal = Terminal::new(backend)?;
+    let events = EventHandler::new(250);
+    let mut tui = Tui::new(terminal, events);
+    tui.init()?;
+
+    // Start the main loop.
+    while app.running {
+        // Render the user interface.
+        tui.draw(&mut app)?;
+        // Handle events.
+        match tui.events.next()? {
+            Event::Tick => app.tick(),
+            Event::Key(key_event) => handle_key_events(key_event, &mut app)?,
+            Event::Mouse(_) => {}
+            Event::Resize(_, _) => {}
+        }
+    }
+
+    // Exit the user interface.
+    tui.exit()?;
+    Ok(())
+}

+ 57 - 0
src/tui.rs

@@ -0,0 +1,57 @@
+use crate::app::{App, AppResult};
+use crate::event::EventHandler;
+use crate::ui;
+use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
+use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
+use std::io;
+use ratatui::backend::Backend;
+use ratatui::Terminal;
+
+/// Representation of a terminal user interface.
+///
+/// It is responsible for setting up the terminal,
+/// initializing the interface and handling the draw events.
+#[derive(Debug)]
+pub struct Tui<B: Backend> {
+    /// Interface to the Terminal.
+    terminal: Terminal<B>,
+    /// Terminal event handler.
+    pub events: EventHandler,
+}
+
+impl<B: Backend> Tui<B> {
+    /// Constructs a new instance of [`Tui`].
+    pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self {
+        Self { terminal, events }
+    }
+
+    /// Initializes the terminal interface.
+    ///
+    /// It enables the raw mode and sets terminal properties.
+    pub fn init(&mut self) -> AppResult<()> {
+        terminal::enable_raw_mode()?;
+        crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?;
+        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) -> AppResult<()> {
+        self.terminal.draw(|frame| ui::render(app, frame))?;
+        Ok(())
+    }
+
+    /// Exits the terminal interface.
+    ///
+    /// It disables the raw mode and reverts back the terminal properties.
+    pub fn exit(&mut self) -> AppResult<()> {
+        terminal::disable_raw_mode()?;
+        crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?;
+        self.terminal.show_cursor()?;
+        Ok(())
+    }
+}

+ 80 - 0
src/ui.rs

@@ -0,0 +1,80 @@
+use ratatui::text::Span;
+use ratatui::widgets::{List, ListItem};
+use ratatui::{
+    backend::Backend,
+    layout::Alignment,
+    style::{Color, Style},
+    text::Line,
+    widgets::{Block, BorderType, Borders},
+    Frame,
+};
+
+use crate::app::App;
+use crate::lumps::{FileType, LumpInfo, LumpType};
+
+fn get_lump_style(lump: &LumpInfo) -> Style {
+    let mut style = Style::default();
+    if lump.lump_type.is_none() {
+        return style;
+    }
+    let info = lump.lump_type.as_ref().unwrap();
+    match info {
+        LumpType::File(file_type) => match file_type {
+            FileType::Map => style = style.fg(Color::White),
+            FileType::Things
+            | FileType::Linedefs
+            | FileType::Sidedefs
+            | FileType::Vertexes
+            | FileType::Segs
+            | FileType::Ssectors
+            | FileType::Nodes
+            | FileType::Sectors
+            | FileType::Reject
+            | FileType::Blockmap
+            | FileType::Behavior => style = style.fg(Color::Gray),
+
+            FileType::Flat => style = style.fg(Color::LightCyan),
+            FileType::Sprite => style = style.fg(Color::Blue),
+            FileType::Texture => style = style.fg(Color::Green),
+            FileType::Sfx => style = style.fg(Color::Yellow),
+        },
+        LumpType::Marker(_, _) => style = style.fg(Color::Red),
+    };
+    style
+}
+
+/// Renders the user interface widgets.
+pub fn render<B: Backend>(app: &mut App, frame: &mut Frame<'_, B>) {
+    // This is where you add new widgets.
+    // See the following resources:
+    // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html
+    // - https://github.com/tui-rs-revival/ratatui/tree/master/examples
+
+    let mut list_size = frame.size();
+    list_size.width = 18;
+
+    let items: Vec<ListItem> = app
+        .lumps
+        .items
+        .iter()
+        .map(|lump| {
+            ListItem::new(Line::from(Line::from(vec![
+                Span::styled(format!("{:8}", lump.name), get_lump_style(&lump)),
+                Span::styled(format!("{:8}", lump.size), Style::default()),
+            ])))
+        })
+        .collect();
+
+    let list = List::new(items)
+        .block(
+            Block::default()
+                .title("Files")
+                .title_alignment(Alignment::Center)
+                .borders(Borders::TOP | Borders::RIGHT)
+                .border_type(BorderType::Plain),
+        )
+        .highlight_style(Style::default().bg(Color::Cyan))
+        .style(Style::default().fg(Color::Cyan).bg(Color::Black));
+
+    frame.render_stateful_widget(list, list_size, &mut app.lumps.state)
+}