From 4d0bf84355837eed26356317e6fcbce3cc54a557 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 20 Jul 2024 22:07:56 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 132 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +++ README.md | 3 + rustfmt.toml | 3 + src/main.rs | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 311 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..47515e2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,132 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "matrixed" +version = "0.1.0" +dependencies = [ + "rand", + "termion", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "termion" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848" +dependencies = [ + "libc", + "libredox", + "numtoa", + "redox_termios", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f43fbc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "matrixed" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8.5" +termion = "4.0.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..debd226 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Matrixed + +Minimal cmatrix-like program that prints text from stdin. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..299948f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ + +hard_tabs = true +max_width = 120 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ada3ed0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,164 @@ +use std::io::{stdin, stdout, BufRead, Write}; +use std::iter::repeat; +use std::sync::mpsc::{self, Sender}; +use std::thread; +use std::time::Duration; +use std::{collections::VecDeque, sync::mpsc::Receiver}; + +use rand::{seq::IteratorRandom, thread_rng}; +use termion::terminal_size; +use termion::{clear, cursor::Goto}; + +fn main() { + let mut screen = Screen::default(); + + let lines = reading_thread(); + let mut queue = None; + + let mut stdout = stdout().lock(); + loop { + // TODO : check for size + let size = get_term_size(); + if screen.dim != size { + screen.resize(size.0, size.1); + } + + if queue.is_none() { + if let Ok(received) = lines.try_recv() { + queue = Some(received); + } + } + + if let Some((text, ok)) = &queue { + let success = screen.append(text.clone()); + if success { + ok.send(()).unwrap(); + queue.take(); + } + } + + screen.step(); + stdout.write_all(screen.draw().as_bytes()).unwrap(); + stdout.flush().unwrap(); + thread::sleep(Duration::from_millis(100)); + } +} + +fn reading_thread() -> Receiver<(String, Sender<()>)> { + let (cmd_tx, cmd_rx) = mpsc::channel(); + thread::spawn(move || { + let mut stdin = stdin().lock(); + loop { + let mut line = String::new(); + stdin.read_line(&mut line).unwrap(); + let (tx, rx) = mpsc::channel(); + cmd_tx.send((line, tx)).unwrap(); + rx.recv().unwrap(); + } + }); + cmd_rx +} + +#[derive(Debug, Default)] +struct Screen { + dim: (usize, usize), + lines: Vec, +} + +impl Screen { + pub fn resize(&mut self, width: usize, height: usize) { + let (old_width, _) = self.dim; + let width_difference = width.saturating_sub(old_width); + let new_lines = repeat(ScreenLine::default()).take(width_difference); + self.lines.truncate(width); + self.lines.extend(new_lines); + self.dim = (width, height); + } + + pub fn append(&mut self, text: String) -> bool { + if let Some(available) = self.find_available_line() { + available.append(text); + true + } else { + false + } + } + + pub fn draw(&self) -> String { + let clear = clear::All; + let mut sequence = format!("{clear}"); + let (_, height) = self.dim; + for (x, line) in self.lines.iter().enumerate() { + for text in &line.content { + for (pos, letter) in text.content.chars().enumerate() { + let y = pos as isize + text.progress; + if y < 0 || y >= (height - 1) as isize { + continue; + } + let goto = Goto((x + 1) as u16, (y + 1) as u16); + sequence += &format!("{goto}{letter}"); + } + } + } + let goto_topleft = Goto(1, 1); + sequence += &format!("{goto_topleft}"); + sequence + } + + pub fn step(&mut self) { + let (_, height) = self.dim; + for line in &mut self.lines.iter_mut() { + line.step(height); + } + } + + fn find_available_line(&mut self) -> Option<&mut ScreenLine> { + self.lines + .iter_mut() + .filter(|l| l.is_available()) + .choose(&mut thread_rng()) + } +} + +#[derive(Debug, Clone, Default)] +struct ScreenLine { + content: VecDeque, +} + +impl ScreenLine { + pub fn is_available(&self) -> bool { + let highest = self.content.back(); + highest.map(|t| t.progress >= 1).unwrap_or(true) + } + + pub fn append(&mut self, text: String) { + let content = text; + let progress = -(content.len() as isize); + self.content.push_back(Text { content, progress }); + } + + pub fn step(&mut self, height: usize) { + for text in &mut self.content { + text.progress += 1; + } + + while let Some(last) = self.content.front() { + if last.progress > height as isize { + self.content.pop_front(); + } else { + break; + } + } + } +} + +#[derive(Debug, Clone)] +struct Text { + pub content: String, + pub progress: isize, +} + +fn get_term_size() -> (usize, usize) { + let (width, height) = terminal_size().unwrap_or((80, 40)); + (width as usize, height as usize) +}