MUSIC PLAYERgit diff --cached src/window_likes/malvim.rs! and fixes

This commit is contained in:
stjet
2024-10-21 05:21:59 +00:00
parent 4a972783a8
commit decf1d3b82
13 changed files with 288 additions and 54 deletions

View File

@@ -10,3 +10,5 @@ blake2 = { version = "0.10.6", default-features = false }
linux_framebuffer = { package = "framebuffer", version = "0.3.1" }
bmp-rust = "0.4.1"
termion = "4.0.3"
rodio = "0.19.0"
rand = "0.8.5"

BIN
bmps/times-new-roman/=4.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

View File

@@ -1,4 +1,5 @@
use std::fs::read_dir;
use std::path::PathBuf;
use bmp_rust::bmp::BMP;
@@ -49,3 +50,16 @@ pub fn get_bmp(path: &str) -> Vec<Vec<Vec<u8>>> {
bmp
}
pub fn get_all_files(dir: PathBuf) -> Vec<PathBuf> {
let mut files = Vec::new();
for entry in read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
files.extend(get_all_files(path));
} else {
files.push(path);
}
}
files
}

View File

@@ -11,7 +11,7 @@ pub enum WindowManagerMessage {
//
}
pub type WindowBox = Box<dyn WindowLike + Send>;
pub type WindowBox = Box<dyn WindowLike>;
/*
impl PartialEq for WindowBox {
@@ -63,6 +63,7 @@ pub enum ShortcutType {
StartMenu,
SwitchWorkspace(u8),
MoveWindowToWorkspace(u8),
FocusPrevWindow,
FocusNextWindow,
QuitWindow,
MoveWindow(Direction),

View File

@@ -1,3 +1,4 @@
use std::path::PathBuf;
pub trait Substring {
fn substring(&self, start: usize, end: usize) -> &str;
@@ -67,4 +68,38 @@ pub fn calc_new_cursor_pos(cursor_pos: usize, new_length: usize) -> usize {
}
}
pub fn concat_paths(current_path: &str, add_path: &str) -> Result<PathBuf, ()> {
let mut new_path = PathBuf::from(current_path);
if add_path.starts_with("/") {
//absolute path
new_path = PathBuf::from(add_path);
} else {
//relative path
for part in add_path.split("/") {
if part == ".." {
if let Some(parent) = new_path.parent() {
new_path = parent.to_path_buf();
} else {
return Err(());
}
} else {
new_path.push(part);
}
}
}
Ok(new_path)
}
//go from seconds to minutes:seconds
pub fn format_seconds(seconds: u64) -> String {
let mut m = (seconds / 60).to_string(); //automatically rounds down
if m.len() == 1 {
m = "0".to_string() + &m;
}
let mut s = (seconds % 60).to_string();
if s.len() == 1 {
s = "0".to_string() + &s;
}
m + ":" + &s
}

View File

@@ -0,0 +1,177 @@
use std::vec::Vec;
use std::vec;
use std::io::BufReader;
use std::path::PathBuf;
use std::fs::File;
use rodio::{ Decoder, OutputStream, Sink, Source };
use rand::prelude::*;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::framebuffer::Dimensions;
use crate::themes::ThemeInfo;
use crate::utils::{ concat_paths, format_seconds };
use crate::fs::get_all_files;
const MONO_WIDTH: u8 = 10;
const LINE_HEIGHT: usize = 18;
#[derive(Default)]
pub struct AudioPlayer {
dimensions: Dimensions,
base_directory: String,
queue: Vec<(PathBuf, u64)>,
stream: Option<Box<OutputStream>>,
sink: Option<Sink>,
command: String,
response: String,
}
impl WindowLike for AudioPlayer {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
WindowMessage::KeyPress(key_press) => {
if key_press.key == '𐘂' { //the enter key
self.response = self.process_command();
self.command = String::new();
} else if key_press.key == '𐘁' { //backspace
if self.command.len() > 0 {
self.command = self.command[..self.command.len() - 1].to_string();
}
} else {
self.command += &key_press.key.to_string();
}
WindowMessageResponse::JustRerender
},
_ => {
WindowMessageResponse::DoNothing
},
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![DrawInstructions::Text([2, self.dimensions[1] - LINE_HEIGHT], "times-new-roman", if self.command.len() > 0 { self.command.clone() } else { self.response.clone() }, theme_info.text, theme_info.background, None, None)];
if let Some(sink) = &self.sink {
let current = &self.queue[self.queue.len() - sink.len()];
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - current_name.len() * MONO_WIDTH as usize / 2, 2], "times-new-romono", current_name.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
let time_string = format!("{}/{}", format_seconds(sink.get_pos().as_secs()), format_seconds(current.1));
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - time_string.len() * MONO_WIDTH as usize / 2, LINE_HEIGHT + 2], "times-new-romono", time_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
}
//
instructions
}
//properties
fn title(&self) -> &'static str {
"Audio Player"
}
fn subtype(&self) -> WindowLikeType {
WindowLikeType::Window
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[500, 200]
}
fn resizable(&self) -> bool {
true
}
}
impl AudioPlayer {
pub fn new() -> Self {
let mut ap: Self = Default::default();
ap.base_directory = "/".to_string();
ap
}
//t: toggle pause/play
//h: prev
//l: next/skip
//j: volume down
//k: volume up
//b <dir>: set base directory
//p <dir>/<playlist file>: play directory or playlist in random order
//just hit enter to refresh
fn process_command(&mut self) -> String {
if self.command.len() == 1 {
if let Some(sink) = &mut self.sink {
if self.command == "t" {
if sink.is_paused() {
sink.play();
return "Resumed".to_string();
} else {
sink.pause();
return "Paused".to_string();
}
} else if self.command == "h" {
//
} else if self.command == "l" {
sink.skip_one();
return "Skipped".to_string();
} else if self.command == "j" {
sink.set_volume(sink.volume() - 0.1);
return "Volume decreased".to_string();
} else if self.command == "k" {
sink.set_volume(sink.volume() + 0.1);
return "Volume increased".to_string();
}
}
} else {
let parts: Vec<&str> = self.command.split(" ").collect();
if self.command.starts_with("p ") {
if parts.len() == 2 {
if let Ok(new_path) = concat_paths(&self.base_directory, parts[1]) {
if new_path.exists() {
if let Some(sink) = &mut self.sink {
sink.clear();
}
let mut queue = if new_path.ends_with(".playlist") {
Vec::new() //placeholder
} else {
get_all_files(PathBuf::from(new_path))
};
let mut rng = rand::thread_rng();
queue.shuffle(&mut rng);
let (stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
self.queue = Vec::new();
for item in &queue {
let file = BufReader::new(File::open(item).unwrap());
let decoded = Decoder::new(file).unwrap();
self.queue.push((item.clone(), decoded.total_duration().unwrap().as_secs()));
sink.append(decoded);
}
self.stream = Some(Box::new(stream));
self.sink = Some(sink);
return "Playing".to_string();
}
}
}
} else if self.command.starts_with("b ") {
if parts.len() == 2 {
if let Ok(new_path) = concat_paths(&self.base_directory, parts[1]) {
if new_path.exists() {
self.base_directory = new_path.to_str().unwrap().to_string();
return "Set new base directory".to_string();
}
}
}
}
}
String::new()
}
}

View File

@@ -264,6 +264,7 @@ impl WindowLike for Malvim {
self.files[self.current_file_index].changed = true;
}
}
self.calc_top_line_pos();
WindowMessageResponse::JustRerender
},
WindowMessage::ChangeDimensions(dimensions) => {
@@ -381,6 +382,21 @@ impl Malvim {
Default::default()
}
fn calc_top_line_pos(&mut self) {
if self.files.len() == 0 {
return;
}
//now, see if the line_pos is still visible from the top_line_pos,
//if not, move top_line_pos down until it is
let current_file = &self.files[self.current_file_index];
let actual_line_pos = self.current.actual_lines.iter().position(|l| l.1 == current_file.line_pos).unwrap();
if current_file.top_line_pos + self.current.max_lines < actual_line_pos {
self.files[self.current_file_index].top_line_pos = actual_line_pos.checked_sub(self.current.max_lines - 1).unwrap_or(0);
} else if actual_line_pos < current_file.top_line_pos {
self.files[self.current_file_index].top_line_pos = actual_line_pos;
}
}
fn calc_current(&mut self) {
if self.files.len() == 0 {
return;
@@ -389,12 +405,7 @@ impl Malvim {
let line_num_width = current_file.content.len().to_string().len() * MONO_WIDTH as usize;
let max_chars_per_line = (self.dimensions[0] - line_num_width - PADDING * 2) / MONO_WIDTH as usize;
let actual_lines = calc_actual_lines(current_file.content.iter(), max_chars_per_line, true);
//now, see if the line_pos is still visible from the top_line_pos,
//if not, move top_line_pos down until it is
let max_lines = (self.dimensions[1] - BAND_HEIGHT * 3 - PADDING) / LINE_HEIGHT;
if current_file.top_line_pos + max_lines < current_file.line_pos {
self.files[self.current_file_index].top_line_pos = current_file.line_pos.checked_sub(max_lines).unwrap_or(0);
}
self.current = Current {
actual_lines,
line_num_width,

View File

@@ -31,7 +31,7 @@ struct MineTile {
}
#[derive(Default, PartialEq)]
enum MinesweeperState {
enum State {
#[default]
Seed,
BeforePlaying,
@@ -43,7 +43,7 @@ enum MinesweeperState {
#[derive(Default)]
pub struct Minesweeper {
dimensions: Dimensions,
state: MinesweeperState,
state: State,
tiles: [[MineTile; 16]; 16],
random_chars: String,
random_seed: u32, //user types in random keyboard stuff at beginning
@@ -58,19 +58,19 @@ impl WindowLike for Minesweeper {
WindowMessageResponse::JustRerender
},
WindowMessage::KeyPress(key_press) => {
if self.state == MinesweeperState::Seed {
if self.state == State::Seed {
if self.random_chars.len() == 4 {
let mut r_chars = self.random_chars.chars();
self.random_seed = ((r_chars.next().unwrap() as u8 as u32) << 24) | ((r_chars.next().unwrap() as u8 as u32) << 16) | ((r_chars.next().unwrap() as u8 as u32) << 8) | (r_chars.next().unwrap() as u8 as u32);
self.random_chars = String::new();
self.state = MinesweeperState::BeforePlaying;
self.state = State::BeforePlaying;
} else {
if u8::try_from(key_press.key).is_ok() {
self.random_chars.push(key_press.key);
}
}
WindowMessageResponse::JustRerender
} else if self.state == MinesweeperState::BeforePlaying || self.state == MinesweeperState::Playing {
} else if self.state == State::BeforePlaying || self.state == State::Playing {
if key_press.key == '𐘁' { //backspace
self.first_char = '\0';
WindowMessageResponse::DoNothing
@@ -84,7 +84,7 @@ impl WindowLike for Minesweeper {
let y = u / 16;
let x = u % 16;
if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
if self.state == MinesweeperState::BeforePlaying {
if self.state == State::BeforePlaying {
loop {
self.new_tiles();
if self.tiles[y][x].touching == 0 && !self.tiles[y][x].mine {
@@ -92,11 +92,11 @@ impl WindowLike for Minesweeper {
}
}
}
self.state = MinesweeperState::Playing;
self.state = State::Playing;
//if that tile not reveal it, reveal it and all adjacent zero touching squares, etc
if self.tiles[y][x].mine {
self.tiles[y][x].revealed = true;
self.state = MinesweeperState::Lost;
self.state = State::Lost;
} else if self.tiles[y][x].touching == 0 {
let mut queue = VecDeque::new();
queue.push_back([x, y]);
@@ -121,7 +121,7 @@ impl WindowLike for Minesweeper {
self.tiles[y][x].revealed = true;
}
self.first_char = '\0';
if self.state != MinesweeperState::Lost {
if self.state != State::Lost {
//check for win
let mut won = true;
for y in 0..16 {
@@ -133,7 +133,7 @@ impl WindowLike for Minesweeper {
}
}
if won {
self.state = MinesweeperState::Won;
self.state = State::Won;
}
}
WindowMessageResponse::JustRerender
@@ -145,7 +145,7 @@ impl WindowLike for Minesweeper {
}
} else {
self.tiles = Default::default();
self.state = MinesweeperState::Seed;
self.state = State::Seed;
WindowMessageResponse::DoNothing
}
},
@@ -154,7 +154,7 @@ impl WindowLike for Minesweeper {
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
if self.state == MinesweeperState::Seed {
if self.state == State::Seed {
vec![
DrawInstructions::Text([4, 4], "times-new-roman", "Type in random characters to initalise the seed".to_string(), theme_info.text, theme_info.background, None, None),
DrawInstructions::Text([4, 4 + 16], "times-new-roman", self.random_chars.clone(), theme_info.text, theme_info.background, None, None),
@@ -230,9 +230,9 @@ impl WindowLike for Minesweeper {
}
}
}
if self.state == MinesweeperState::Lost {
if self.state == State::Lost {
instructions.extend(vec![DrawInstructions::Text([4, 4], "times-new-roman", "You LOST!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
} else if self.state == MinesweeperState::Won {
} else if self.state == State::Won {
instructions.extend(vec![DrawInstructions::Text([4, 4], "times-new-roman", "You WON!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
}
instructions
@@ -240,6 +240,7 @@ impl WindowLike for Minesweeper {
}
//properties
fn title(&self) -> &'static str {
"Minesweeper"
}

View File

@@ -7,4 +7,5 @@ pub mod workspace_indicator;
pub mod minesweeper;
pub mod terminal;
pub mod malvim;
pub mod audio_player;

View File

@@ -143,8 +143,10 @@ impl StartMenu {
to_add.push("Minesweeper");
} else if name == "Editing" {
to_add.push("Malvim");
} else if name == "Files" {
} else if name == "Utils" {
to_add.push("Terminal");
} else if name == "Files" {
to_add.push("Audio Player");
}
//
for a in 0..to_add.len() {

View File

@@ -2,13 +2,13 @@ use std::vec::Vec;
use std::vec;
use std::process::{ Command, Output };
use std::str::from_utf8;
use std::path::PathBuf;
use std::io;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::framebuffer::Dimensions;
use crate::themes::ThemeInfo;
use crate::utils::concat_paths;
const MONO_WIDTH: u8 = 10;
const LINE_HEIGHT: usize = 15;
@@ -41,6 +41,10 @@ impl WindowLike for Terminal {
self.calc_actual_lines();
WindowMessageResponse::JustRerender
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
WindowMessage::KeyPress(key_press) => {
if key_press.key == '𐘁' { //backspace
if self.current_input.len() > 0 {
@@ -72,11 +76,6 @@ impl WindowLike for Terminal {
self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0);
WindowMessageResponse::JustRerender
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
//
_ => WindowMessageResponse::DoNothing,
}
}
@@ -132,28 +131,8 @@ impl Terminal {
} else if self.current_input.starts_with("cd ") {
let mut cd_split = self.current_input.split(" ");
cd_split.next().unwrap();
let mut failed = false;
let arg = cd_split.next().unwrap();
let mut new_path = PathBuf::from(&self.current_path);
if arg.starts_with("/") {
//absolute path
new_path = PathBuf::from(arg);
} else {
//relative path
for part in arg.split("/") {
if part == ".." {
if let Some(parent) = new_path.parent() {
new_path = parent.to_path_buf();
} else {
failed = true;
}
} else {
new_path.push(part);
}
}
}
if !failed {
//see if path exists
if let Ok(new_path) = concat_paths(&self.current_path, arg) {
if new_path.exists() {
self.current_path = new_path.to_str().unwrap().to_string();
}

View File

@@ -25,6 +25,7 @@ use crate::window_likes::start_menu::StartMenu;
use crate::window_likes::minesweeper::Minesweeper;
use crate::window_likes::terminal::Terminal;
use crate::window_likes::malvim::Malvim;
use crate::window_likes::audio_player::AudioPlayer;
//todo, better error handling for windows
@@ -112,7 +113,7 @@ pub enum Workspace {
pub struct WindowLikeInfo {
id: usize,
window_like: Box<dyn WindowLike + Send>,
window_like: WindowBox,
top_left: Point,
dimensions: Dimensions,
workspace: Workspace,
@@ -154,7 +155,7 @@ impl WindowManager {
wm
}
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike + Send>, top_left: Point, dimensions: Option<Dimensions>) {
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike>, top_left: Point, dimensions: Option<Dimensions>) {
let subtype = window_like.subtype();
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
self.id_count = self.id_count + 1;
@@ -250,6 +251,7 @@ impl WindowManager {
let shortcuts = HashMap::from([
//alt+e is terminate program (ctrl+c)
('s', ShortcutType::StartMenu),
('[', ShortcutType::FocusPrevWindow),
(']', ShortcutType::FocusNextWindow),
('q', ShortcutType::QuitWindow),
('c', ShortcutType::CenterWindow),
@@ -377,14 +379,22 @@ impl WindowManager {
}
}
},
&ShortcutType::FocusNextWindow => {
&ShortcutType::FocusPrevWindow | &ShortcutType::FocusNextWindow => {
let current_index = self.get_focused_index().unwrap_or(0);
let mut new_focus_index = current_index;
loop {
if shortcut == &ShortcutType::FocusPrevWindow {
if new_focus_index == 0 {
new_focus_index = self.window_infos.len() - 1;
} else {
new_focus_index -= 1;
}
} else {
new_focus_index += 1;
if new_focus_index == self.window_infos.len() {
new_focus_index = 0;
}
}
if self.window_infos[new_focus_index].window_like.subtype() == WindowLikeType::Window && self.window_infos[new_focus_index].workspace == Workspace::Workspace(self.current_workspace) {
//switch focus to this
self.focused_id = self.window_infos[new_focus_index].id;
@@ -494,6 +504,7 @@ impl WindowManager {
"Minesweeper" => Box::new(Minesweeper::new()),
"Malvim" => Box::new(Malvim::new()),
"Terminal" => Box::new(Terminal::new()),
"Audio Player" => Box::new(AudioPlayer::new()),
"StartMenu" => Box::new(StartMenu::new()),
_ => panic!("no such window"),
};