Initial commit

This commit is contained in:
Rolf Martin Glomsrud 2025-05-01 20:00:17 +02:00
parent 7045abcbc4
commit 8928401233
8 changed files with 330 additions and 0 deletions

5
.gitignore vendored
View file

@ -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

11
Cargo.toml Normal file
View file

@ -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"

View file

@ -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);
}
}
*/

3
src/algorithm/mod.rs Normal file
View file

@ -0,0 +1,3 @@
use crate::drawableVec::DrawableVec;
pub mod insertSort;

59
src/drawableVec.rs Normal file
View file

@ -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<u32>,
event_rx: UnboundedReceiver<SortingMessage>,
event_tx: UnboundedSender<Message>
}
impl DrawableVec {
pub fn new(event_tx: UnboundedSender<Message>, event_rx: UnboundedReceiver<SortingMessage>, vec: Vec<u32>) -> 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]
}
}

39
src/graphics.rs Normal file
View file

@ -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<Bar> = 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)
}

33
src/main.rs Normal file
View file

@ -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(())
}

142
src/tui.rs Normal file
View file

@ -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<CrosstermBackend<std::io::Stdout>>,
tick_rate: f64,
frame_rate: f64,
event_rx: UnboundedReceiver<Message>,
event_tx: UnboundedSender<Message>,
sorting_tx: UnboundedSender<SortingMessage>,
should_exit: bool,
vec: Vec<u32>,
}
use crate::{drawableVec::{self, DrawableVec, SortingMessage}, graphics::vertical_barchart};
#[derive(Clone, Debug)]
pub enum UpdateCommand {
None,
Quit,
}
impl Tui{
pub fn new() -> Result<Self>{
let terminal = ratatui::init();
let (event_tx, event_rx):(UnboundedSender<Message>,UnboundedReceiver<Message>) = 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<UpdateCommand> {
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(())
}
}