MUSIC PLAYERgit diff --cached src/window_likes/malvim.rs! and fixes
This commit is contained in:
@@ -10,3 +10,5 @@ blake2 = { version = "0.10.6", default-features = false }
|
|||||||
linux_framebuffer = { package = "framebuffer", version = "0.3.1" }
|
linux_framebuffer = { package = "framebuffer", version = "0.3.1" }
|
||||||
bmp-rust = "0.4.1"
|
bmp-rust = "0.4.1"
|
||||||
termion = "4.0.3"
|
termion = "4.0.3"
|
||||||
|
rodio = "0.19.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|||||||
BIN
bmps/times-new-roman/=4.bmp
Normal file
BIN
bmps/times-new-roman/=4.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 B |
BIN
bmps/times-new-romono/=4.bmp
Normal file
BIN
bmps/times-new-romono/=4.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 B |
14
src/fs.rs
14
src/fs.rs
@@ -1,4 +1,5 @@
|
|||||||
use std::fs::read_dir;
|
use std::fs::read_dir;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bmp_rust::bmp::BMP;
|
use bmp_rust::bmp::BMP;
|
||||||
|
|
||||||
@@ -49,3 +50,16 @@ pub fn get_bmp(path: &str) -> Vec<Vec<Vec<u8>>> {
|
|||||||
bmp
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub enum WindowManagerMessage {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type WindowBox = Box<dyn WindowLike + Send>;
|
pub type WindowBox = Box<dyn WindowLike>;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
impl PartialEq for WindowBox {
|
impl PartialEq for WindowBox {
|
||||||
@@ -63,6 +63,7 @@ pub enum ShortcutType {
|
|||||||
StartMenu,
|
StartMenu,
|
||||||
SwitchWorkspace(u8),
|
SwitchWorkspace(u8),
|
||||||
MoveWindowToWorkspace(u8),
|
MoveWindowToWorkspace(u8),
|
||||||
|
FocusPrevWindow,
|
||||||
FocusNextWindow,
|
FocusNextWindow,
|
||||||
QuitWindow,
|
QuitWindow,
|
||||||
MoveWindow(Direction),
|
MoveWindow(Direction),
|
||||||
|
|||||||
35
src/utils.rs
35
src/utils.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub trait Substring {
|
pub trait Substring {
|
||||||
fn substring(&self, start: usize, end: usize) -> &str;
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
177
src/window_likes/audio_player.rs
Normal file
177
src/window_likes/audio_player.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -264,6 +264,7 @@ impl WindowLike for Malvim {
|
|||||||
self.files[self.current_file_index].changed = true;
|
self.files[self.current_file_index].changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.calc_top_line_pos();
|
||||||
WindowMessageResponse::JustRerender
|
WindowMessageResponse::JustRerender
|
||||||
},
|
},
|
||||||
WindowMessage::ChangeDimensions(dimensions) => {
|
WindowMessage::ChangeDimensions(dimensions) => {
|
||||||
@@ -381,6 +382,21 @@ impl Malvim {
|
|||||||
Default::default()
|
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) {
|
fn calc_current(&mut self) {
|
||||||
if self.files.len() == 0 {
|
if self.files.len() == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -389,12 +405,7 @@ impl Malvim {
|
|||||||
let line_num_width = current_file.content.len().to_string().len() * MONO_WIDTH as usize;
|
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 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);
|
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;
|
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 {
|
self.current = Current {
|
||||||
actual_lines,
|
actual_lines,
|
||||||
line_num_width,
|
line_num_width,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct MineTile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
enum MinesweeperState {
|
enum State {
|
||||||
#[default]
|
#[default]
|
||||||
Seed,
|
Seed,
|
||||||
BeforePlaying,
|
BeforePlaying,
|
||||||
@@ -43,7 +43,7 @@ enum MinesweeperState {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Minesweeper {
|
pub struct Minesweeper {
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
state: MinesweeperState,
|
state: State,
|
||||||
tiles: [[MineTile; 16]; 16],
|
tiles: [[MineTile; 16]; 16],
|
||||||
random_chars: String,
|
random_chars: String,
|
||||||
random_seed: u32, //user types in random keyboard stuff at beginning
|
random_seed: u32, //user types in random keyboard stuff at beginning
|
||||||
@@ -58,19 +58,19 @@ impl WindowLike for Minesweeper {
|
|||||||
WindowMessageResponse::JustRerender
|
WindowMessageResponse::JustRerender
|
||||||
},
|
},
|
||||||
WindowMessage::KeyPress(key_press) => {
|
WindowMessage::KeyPress(key_press) => {
|
||||||
if self.state == MinesweeperState::Seed {
|
if self.state == State::Seed {
|
||||||
if self.random_chars.len() == 4 {
|
if self.random_chars.len() == 4 {
|
||||||
let mut r_chars = self.random_chars.chars();
|
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_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.random_chars = String::new();
|
||||||
self.state = MinesweeperState::BeforePlaying;
|
self.state = State::BeforePlaying;
|
||||||
} else {
|
} else {
|
||||||
if u8::try_from(key_press.key).is_ok() {
|
if u8::try_from(key_press.key).is_ok() {
|
||||||
self.random_chars.push(key_press.key);
|
self.random_chars.push(key_press.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowMessageResponse::JustRerender
|
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
|
if key_press.key == '𐘁' { //backspace
|
||||||
self.first_char = '\0';
|
self.first_char = '\0';
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
@@ -84,7 +84,7 @@ impl WindowLike for Minesweeper {
|
|||||||
let y = u / 16;
|
let y = u / 16;
|
||||||
let x = u % 16;
|
let x = u % 16;
|
||||||
if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
|
if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() {
|
||||||
if self.state == MinesweeperState::BeforePlaying {
|
if self.state == State::BeforePlaying {
|
||||||
loop {
|
loop {
|
||||||
self.new_tiles();
|
self.new_tiles();
|
||||||
if self.tiles[y][x].touching == 0 && !self.tiles[y][x].mine {
|
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 that tile not reveal it, reveal it and all adjacent zero touching squares, etc
|
||||||
if self.tiles[y][x].mine {
|
if self.tiles[y][x].mine {
|
||||||
self.tiles[y][x].revealed = true;
|
self.tiles[y][x].revealed = true;
|
||||||
self.state = MinesweeperState::Lost;
|
self.state = State::Lost;
|
||||||
} else if self.tiles[y][x].touching == 0 {
|
} else if self.tiles[y][x].touching == 0 {
|
||||||
let mut queue = VecDeque::new();
|
let mut queue = VecDeque::new();
|
||||||
queue.push_back([x, y]);
|
queue.push_back([x, y]);
|
||||||
@@ -121,7 +121,7 @@ impl WindowLike for Minesweeper {
|
|||||||
self.tiles[y][x].revealed = true;
|
self.tiles[y][x].revealed = true;
|
||||||
}
|
}
|
||||||
self.first_char = '\0';
|
self.first_char = '\0';
|
||||||
if self.state != MinesweeperState::Lost {
|
if self.state != State::Lost {
|
||||||
//check for win
|
//check for win
|
||||||
let mut won = true;
|
let mut won = true;
|
||||||
for y in 0..16 {
|
for y in 0..16 {
|
||||||
@@ -133,7 +133,7 @@ impl WindowLike for Minesweeper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if won {
|
if won {
|
||||||
self.state = MinesweeperState::Won;
|
self.state = State::Won;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowMessageResponse::JustRerender
|
WindowMessageResponse::JustRerender
|
||||||
@@ -145,7 +145,7 @@ impl WindowLike for Minesweeper {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.tiles = Default::default();
|
self.tiles = Default::default();
|
||||||
self.state = MinesweeperState::Seed;
|
self.state = State::Seed;
|
||||||
WindowMessageResponse::DoNothing
|
WindowMessageResponse::DoNothing
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -154,7 +154,7 @@ impl WindowLike for Minesweeper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||||
if self.state == MinesweeperState::Seed {
|
if self.state == State::Seed {
|
||||||
vec![
|
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], "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),
|
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)]);
|
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.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
|
instructions
|
||||||
@@ -240,6 +240,7 @@ impl WindowLike for Minesweeper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//properties
|
//properties
|
||||||
|
|
||||||
fn title(&self) -> &'static str {
|
fn title(&self) -> &'static str {
|
||||||
"Minesweeper"
|
"Minesweeper"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ pub mod workspace_indicator;
|
|||||||
pub mod minesweeper;
|
pub mod minesweeper;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
pub mod malvim;
|
pub mod malvim;
|
||||||
|
pub mod audio_player;
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,10 @@ impl StartMenu {
|
|||||||
to_add.push("Minesweeper");
|
to_add.push("Minesweeper");
|
||||||
} else if name == "Editing" {
|
} else if name == "Editing" {
|
||||||
to_add.push("Malvim");
|
to_add.push("Malvim");
|
||||||
} else if name == "Files" {
|
} else if name == "Utils" {
|
||||||
to_add.push("Terminal");
|
to_add.push("Terminal");
|
||||||
|
} else if name == "Files" {
|
||||||
|
to_add.push("Audio Player");
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
for a in 0..to_add.len() {
|
for a in 0..to_add.len() {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use std::vec::Vec;
|
|||||||
use std::vec;
|
use std::vec;
|
||||||
use std::process::{ Command, Output };
|
use std::process::{ Command, Output };
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
|
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||||
use crate::messages::{ WindowMessage, WindowMessageResponse };
|
use crate::messages::{ WindowMessage, WindowMessageResponse };
|
||||||
use crate::framebuffer::Dimensions;
|
use crate::framebuffer::Dimensions;
|
||||||
use crate::themes::ThemeInfo;
|
use crate::themes::ThemeInfo;
|
||||||
|
use crate::utils::concat_paths;
|
||||||
|
|
||||||
const MONO_WIDTH: u8 = 10;
|
const MONO_WIDTH: u8 = 10;
|
||||||
const LINE_HEIGHT: usize = 15;
|
const LINE_HEIGHT: usize = 15;
|
||||||
@@ -41,6 +41,10 @@ impl WindowLike for Terminal {
|
|||||||
self.calc_actual_lines();
|
self.calc_actual_lines();
|
||||||
WindowMessageResponse::JustRerender
|
WindowMessageResponse::JustRerender
|
||||||
},
|
},
|
||||||
|
WindowMessage::ChangeDimensions(dimensions) => {
|
||||||
|
self.dimensions = dimensions;
|
||||||
|
WindowMessageResponse::JustRerender
|
||||||
|
},
|
||||||
WindowMessage::KeyPress(key_press) => {
|
WindowMessage::KeyPress(key_press) => {
|
||||||
if key_press.key == '𐘁' { //backspace
|
if key_press.key == '𐘁' { //backspace
|
||||||
if self.current_input.len() > 0 {
|
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);
|
self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0);
|
||||||
WindowMessageResponse::JustRerender
|
WindowMessageResponse::JustRerender
|
||||||
},
|
},
|
||||||
WindowMessage::ChangeDimensions(dimensions) => {
|
|
||||||
self.dimensions = dimensions;
|
|
||||||
WindowMessageResponse::JustRerender
|
|
||||||
},
|
|
||||||
//
|
|
||||||
_ => WindowMessageResponse::DoNothing,
|
_ => WindowMessageResponse::DoNothing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,28 +131,8 @@ impl Terminal {
|
|||||||
} else if self.current_input.starts_with("cd ") {
|
} else if self.current_input.starts_with("cd ") {
|
||||||
let mut cd_split = self.current_input.split(" ");
|
let mut cd_split = self.current_input.split(" ");
|
||||||
cd_split.next().unwrap();
|
cd_split.next().unwrap();
|
||||||
let mut failed = false;
|
|
||||||
let arg = cd_split.next().unwrap();
|
let arg = cd_split.next().unwrap();
|
||||||
let mut new_path = PathBuf::from(&self.current_path);
|
if let Ok(new_path) = concat_paths(&self.current_path, arg) {
|
||||||
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 new_path.exists() {
|
if new_path.exists() {
|
||||||
self.current_path = new_path.to_str().unwrap().to_string();
|
self.current_path = new_path.to_str().unwrap().to_string();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use crate::window_likes::start_menu::StartMenu;
|
|||||||
use crate::window_likes::minesweeper::Minesweeper;
|
use crate::window_likes::minesweeper::Minesweeper;
|
||||||
use crate::window_likes::terminal::Terminal;
|
use crate::window_likes::terminal::Terminal;
|
||||||
use crate::window_likes::malvim::Malvim;
|
use crate::window_likes::malvim::Malvim;
|
||||||
|
use crate::window_likes::audio_player::AudioPlayer;
|
||||||
|
|
||||||
//todo, better error handling for windows
|
//todo, better error handling for windows
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ pub enum Workspace {
|
|||||||
|
|
||||||
pub struct WindowLikeInfo {
|
pub struct WindowLikeInfo {
|
||||||
id: usize,
|
id: usize,
|
||||||
window_like: Box<dyn WindowLike + Send>,
|
window_like: WindowBox,
|
||||||
top_left: Point,
|
top_left: Point,
|
||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
@@ -154,7 +155,7 @@ impl WindowManager {
|
|||||||
wm
|
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 subtype = window_like.subtype();
|
||||||
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
|
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
|
||||||
self.id_count = self.id_count + 1;
|
self.id_count = self.id_count + 1;
|
||||||
@@ -250,6 +251,7 @@ impl WindowManager {
|
|||||||
let shortcuts = HashMap::from([
|
let shortcuts = HashMap::from([
|
||||||
//alt+e is terminate program (ctrl+c)
|
//alt+e is terminate program (ctrl+c)
|
||||||
('s', ShortcutType::StartMenu),
|
('s', ShortcutType::StartMenu),
|
||||||
|
('[', ShortcutType::FocusPrevWindow),
|
||||||
(']', ShortcutType::FocusNextWindow),
|
(']', ShortcutType::FocusNextWindow),
|
||||||
('q', ShortcutType::QuitWindow),
|
('q', ShortcutType::QuitWindow),
|
||||||
('c', ShortcutType::CenterWindow),
|
('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 current_index = self.get_focused_index().unwrap_or(0);
|
||||||
let mut new_focus_index = current_index;
|
let mut new_focus_index = current_index;
|
||||||
loop {
|
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;
|
new_focus_index += 1;
|
||||||
if new_focus_index == self.window_infos.len() {
|
if new_focus_index == self.window_infos.len() {
|
||||||
new_focus_index = 0;
|
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) {
|
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
|
//switch focus to this
|
||||||
self.focused_id = self.window_infos[new_focus_index].id;
|
self.focused_id = self.window_infos[new_focus_index].id;
|
||||||
@@ -494,6 +504,7 @@ impl WindowManager {
|
|||||||
"Minesweeper" => Box::new(Minesweeper::new()),
|
"Minesweeper" => Box::new(Minesweeper::new()),
|
||||||
"Malvim" => Box::new(Malvim::new()),
|
"Malvim" => Box::new(Malvim::new()),
|
||||||
"Terminal" => Box::new(Terminal::new()),
|
"Terminal" => Box::new(Terminal::new()),
|
||||||
|
"Audio Player" => Box::new(AudioPlayer::new()),
|
||||||
"StartMenu" => Box::new(StartMenu::new()),
|
"StartMenu" => Box::new(StartMenu::new()),
|
||||||
_ => panic!("no such window"),
|
_ => panic!("no such window"),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user