From 89284012334715009a951fb42dbf7c141b24c0d0 Mon Sep 17 00:00:00 2001 From: polsevev Date: Thu, 1 May 2025 20:00:17 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 ++ Cargo.toml | 11 +++ src/algorithm/insertSort.rs | 38 ++++++++++ src/algorithm/mod.rs | 3 + src/drawableVec.rs | 59 +++++++++++++++ src/graphics.rs | 39 ++++++++++ src/main.rs | 33 +++++++++ src/tui.rs | 142 ++++++++++++++++++++++++++++++++++++ 8 files changed, 330 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/algorithm/insertSort.rs create mode 100644 src/algorithm/mod.rs create mode 100644 src/drawableVec.rs create mode 100644 src/graphics.rs create mode 100644 src/main.rs create mode 100644 src/tui.rs diff --git a/.gitignore b/.gitignore index ab951f8..620f8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,8 @@ Cargo.lock # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..50d6b32 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sortingTUI" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1", features = ["full"] } +color-eyre = "0.6.3" +crossterm = "0.28.1" +rand = "0.9.1" +ratatui = "0.29.0" diff --git a/src/algorithm/insertSort.rs b/src/algorithm/insertSort.rs new file mode 100644 index 0000000..b783b1d --- /dev/null +++ b/src/algorithm/insertSort.rs @@ -0,0 +1,38 @@ +use crate::drawableVec::DrawableVec; + +use color_eyre::Result; +pub async fn sort(list: &mut DrawableVec) -> Result<()> { + for index in 0..list.len() { + let mut j = index; + while j > 0 && list.lessThan(j, j - 1) { + list.swap(j, j - 1).await?; + j -= 1; + } + } + Ok(()) +} + +/* +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use crate::GuiHookVec::NonGuiVec; + +use super::*; + + + macro_rules! aw { + ($e:expr) => { + tokio_test::block_on($e) + }; + } + + #[test] + fn insertsort_correct() { + let mut list:NonGuiVec = aw!(SortingList::new(1000,0.0)); + aw!(insertSort(&mut list)); + assert_eq!( list.isSorted(), true); + } +} + +*/ diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs new file mode 100644 index 0000000..94a6bf8 --- /dev/null +++ b/src/algorithm/mod.rs @@ -0,0 +1,3 @@ +use crate::drawableVec::DrawableVec; + +pub mod insertSort; diff --git a/src/drawableVec.rs b/src/drawableVec.rs new file mode 100644 index 0000000..a45ae61 --- /dev/null +++ b/src/drawableVec.rs @@ -0,0 +1,59 @@ +use std::time::Duration; + +use color_eyre::{eyre::Ok, Result}; +use ratatui::{ + DefaultTerminal, Frame, + crossterm::event::{self, Event, KeyCode, KeyEventKind}, + layout::{Constraint, Direction, Layout}, + style::{Color, Style, Stylize}, + text::Line, + widgets::{Bar, BarChart, BarGroup, Block}, +}; +use tokio::{sync::mpsc::{UnboundedReceiver, UnboundedSender}, time::sleep}; + +use crate::{ + algorithm::{self, insertSort::{self, sort}}, + graphics::{self, vertical_barchart}, tui::{self, Message}, +}; + +pub enum SortingMessage{ + Tick +} + +pub struct DrawableVec { + vec: Vec, + event_rx: UnboundedReceiver, + event_tx: UnboundedSender +} + +impl DrawableVec { + pub fn new(event_tx: UnboundedSender, event_rx: UnboundedReceiver, vec: Vec) -> DrawableVec { + return DrawableVec { + vec, + event_rx, + event_tx: event_tx.clone() + }; + } + + pub async fn run(mut self) -> Result<()> { + insertSort::sort(&mut self).await?; + sleep(Duration::from_secs(100000)).await; + Ok(()) + } + + pub fn len(&self) -> usize { + return self.vec.len(); + } + + pub async fn swap(&mut self, a: usize, b: usize) -> Result<()> { + self.vec.swap(a, b); + self.event_tx.send(Message::SwapEvent(a, b))?; + + self.event_rx.recv().await; + Ok(()) + } + + pub fn lessThan(&self, a: usize, b: usize) -> bool { + self.vec[a] < self.vec[b] + } +} diff --git a/src/graphics.rs b/src/graphics.rs new file mode 100644 index 0000000..265d9f1 --- /dev/null +++ b/src/graphics.rs @@ -0,0 +1,39 @@ +use ratatui::{ + crossterm::event::{self, Event, KeyCode, KeyEventKind}, + layout::{Constraint, Direction, Layout}, + style::{Color, Style, Stylize}, + text::Line, + widgets::{Bar, BarChart, BarGroup, Block}, + DefaultTerminal, Frame, +}; +/// Create a vertical bar chart from the temperatures data. +pub fn vertical_barchart(temperatures: &[u32]) -> BarChart { + let bars: Vec = temperatures + .iter() + .enumerate() + .map(|(hour, value)| vertical_bar(hour, value)) + .collect(); + let title = Line::from("Weather (Vertical)").centered(); + BarChart::default() + .data(BarGroup::default().bars(&bars)) + .block(Block::new().title(title)) + .bar_width(5) +} + +pub fn vertical_bar(hour: usize, temperature: &u32) -> Bar { + Bar::default() + .value(u64::from(*temperature)) + .label(Line::from(format!("{hour:>02}:00"))) + .text_value(format!("{temperature:>3}°")) + .style(temperature_style(*temperature)) + .value_style(temperature_style(*temperature).reversed()) +} + + + +/// create a yellow to red value based on the value (50-90) +pub fn temperature_style(value: u32) -> Style { + let green = (255.0 * (1.0 - f64::from((value % 10)) / 40.0)) as u8; + let color = Color::Rgb(255, green, 0); + Style::new().fg(color) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9a85adb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,33 @@ +use color_eyre::Result; +use drawableVec::DrawableVec; +use rand::{thread_rng, Rng}; +use ratatui::{ + crossterm::event::{self, Event, KeyCode, KeyEventKind}, + layout::{Constraint, Direction, Layout}, + style::{Color, Style, Stylize}, + text::Line, + widgets::{Bar, BarChart, BarGroup, Block}, + DefaultTerminal, Frame, +}; +use tui::Tui; + +mod graphics; +mod drawableVec; + +mod algorithm; +mod tui; + + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install()?; + let mut binding = Tui::new()?; + binding.run().await?; + + ratatui::restore(); + Ok(()) +} + + + + diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..0d7cc91 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,142 @@ +use std::{time::Duration}; + +use crossterm::event::{Event, KeyCode, KeyEventKind, MouseEventKind}; +use rand::seq::SliceRandom; +use ratatui::{layout::{Constraint, Layout}, prelude::CrosstermBackend, style::Stylize, Terminal}; +use tokio::{sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, task::JoinHandle, time}; +use color_eyre::{eyre::Ok, Result}; +pub enum Message { + Quit, + Tick, + Render, + StartSimulation, + SwapEvent(usize, usize) +} + +pub struct Tui{ + terminal: Terminal>, + tick_rate: f64, + frame_rate: f64, + event_rx: UnboundedReceiver, + event_tx: UnboundedSender, + sorting_tx: UnboundedSender, + should_exit: bool, + vec: Vec, +} +use crate::{drawableVec::{self, DrawableVec, SortingMessage}, graphics::vertical_barchart}; + + +#[derive(Clone, Debug)] +pub enum UpdateCommand { + None, + Quit, +} + +impl Tui{ + pub fn new() -> Result{ + let terminal = ratatui::init(); + let (event_tx, event_rx):(UnboundedSender,UnboundedReceiver) = mpsc::unbounded_channel(); + let (sorting_tx, sorting_rx) = mpsc::unbounded_channel(); + let mut vec = (Vec::from_iter(1..100)); + vec.shuffle(&mut rand::rng()); + let drawableVec = DrawableVec::new(event_tx.clone(),sorting_rx, vec.clone()); + + let sorter = tokio::spawn(drawableVec.run()); + + + Ok(Self{ + terminal, + tick_rate: 10., + frame_rate: 60., + event_rx, + event_tx, + sorting_tx, + should_exit: false, + vec: vec, + }) + } + + pub async fn run(&mut self) -> Result<()>{ + let tick_rate = Duration::from_secs_f64(1.0 / self.tick_rate); + let frame_rate = Duration::from_secs_f64(1.0 / self.frame_rate); + let mut tick_interval = time::interval(tick_rate); + let mut frame_interval = time::interval(frame_rate); + while !self.should_exit { + tokio::select! { + _tick = tick_interval.tick() => { + self.event_tx.send(Message::Tick)?; + self.sorting_tx.send(SortingMessage::Tick)? + } + _frame = frame_interval.tick() => { + self.event_tx.send(Message::Render)? + } + Some(message) = self.event_rx.recv() => { + match self.update(message).await? { + UpdateCommand::Quit => return { + self.should_exit = true; + Ok(()) + }, + UpdateCommand::None => continue, + } + } + Result::Ok(ready) = tokio::task::spawn_blocking(|| crossterm::event::poll(Duration::from_millis(100))) => { + match ready? { + true => { + let event = crossterm::event::read()?; + self.handle_event(event)?; + } + false => continue, + } + } + } + } + Ok(()) + } + + async fn update(&mut self, message: Message) -> Result { + match message { + Message::Quit => Ok(UpdateCommand::Quit), + Message::Tick => { + Ok(UpdateCommand::None) + } + Message::Render => { + self.view()?; + Ok(UpdateCommand::None) + } + Message::StartSimulation => todo!(), + Message::SwapEvent(a,b) => {self.vec.swap(a, b); Ok(UpdateCommand::None)}, + } + } + + + fn view(&mut self) -> Result<()> { + self.terminal.draw(|frame| { + let [title, vertical, horizontal] = Layout::vertical([ + Constraint::Length(1), + ratatui::layout::Constraint::Fill(1), + Constraint::Fill(1), + ]) + .spacing(1) + .areas(frame.area()); + frame.render_widget("Sorting!".bold().into_centered_line(), title); + frame.render_widget(vertical_barchart(&self.vec), horizontal); + })?; + + Ok(()) + } + + fn handle_event(&self, event: Event) -> Result<()> { + match event { + Event::Key(key) => { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Esc { + self.event_tx.send(Message::Quit)?; + }else if key.kind == KeyEventKind::Press && key.code == KeyCode:: Enter { + self.event_tx.send(Message::StartSimulation)?; + } + } + _ => {} + } + Ok(()) + } + +} \ No newline at end of file