handle window crashes, various fixes
add reversi, fixes for audio player and terminal
This commit is contained in:
@@ -2,7 +2,7 @@ use std::vec::Vec;
|
||||
use std::vec;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::fs::{ read_to_string, File };
|
||||
|
||||
use rodio::{ Decoder, OutputStream, Sink, Source };
|
||||
use rand::prelude::*;
|
||||
@@ -19,7 +19,7 @@ const MONO_WIDTH: u8 = 10;
|
||||
const LINE_HEIGHT: usize = 18;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AudioPlayer {
|
||||
struct AudioPlayer {
|
||||
dimensions: Dimensions,
|
||||
base_directory: String,
|
||||
queue: Vec<(PathBuf, u64)>,
|
||||
@@ -139,8 +139,18 @@ impl AudioPlayer {
|
||||
if let Some(sink) = &mut self.sink {
|
||||
sink.clear();
|
||||
}
|
||||
let mut queue = if new_path.ends_with(".playlist") {
|
||||
Vec::new() //placeholder
|
||||
let mut queue = if parts[1].ends_with(".playlist") {
|
||||
let mut queue = Vec::new();
|
||||
let contents = read_to_string(new_path).unwrap();
|
||||
for line in contents.split("\n") {
|
||||
//todo: handle more edge cases later
|
||||
if line.ends_with("/*") {
|
||||
queue.extend(get_all_files(concat_paths(&self.base_directory, &line[..line.len() - 2]).unwrap()));
|
||||
} else if line.len() > 0 {
|
||||
queue.push(concat_paths(&self.base_directory, &(line.to_owned() + ".mp3")).unwrap());
|
||||
}
|
||||
}
|
||||
queue
|
||||
} else {
|
||||
get_all_files(PathBuf::from(new_path))
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ struct Current {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Malvim {
|
||||
struct Malvim {
|
||||
dimensions: Dimensions,
|
||||
state: State,
|
||||
mode: Mode,
|
||||
|
||||
@@ -42,7 +42,7 @@ enum State {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Minesweeper {
|
||||
struct Minesweeper {
|
||||
dimensions: Dimensions,
|
||||
state: State,
|
||||
tiles: [[MineTile; 16]; 16],
|
||||
|
||||
111
src/bin/reversi.rs
Normal file
111
src/bin/reversi.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::vec::Vec;
|
||||
use std::vec;
|
||||
use std::fmt;
|
||||
|
||||
use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm::messages::{ WindowMessage, WindowMessageResponse };
|
||||
use ming_wm::framebuffer::{ Dimensions, RGBColor };
|
||||
use ming_wm::themes::ThemeInfo;
|
||||
use ming_wm::ipc::listen;
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
enum Tile {
|
||||
#[default]
|
||||
Empty,
|
||||
White,
|
||||
Black,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub fn to_color(&self) -> Option<RGBColor> {
|
||||
match self {
|
||||
Tile::Empty => None,
|
||||
Tile::White => Some([255, 255, 255]),
|
||||
Tile::Black => Some([0, 0, 0]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Reversi {
|
||||
dimensions: Dimensions,
|
||||
tiles: [[Tile; 8]; 8],
|
||||
//
|
||||
}
|
||||
|
||||
impl WindowLike for Reversi {
|
||||
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
|
||||
match message {
|
||||
WindowMessage::Init(dimensions) => {
|
||||
self.dimensions = dimensions;
|
||||
self.new_tiles();
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
//
|
||||
_ => WindowMessageResponse::DoNothing,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||
let mut instructions = vec![
|
||||
DrawInstructions::Rect([0, 0], self.dimensions, [72, 93, 63]),
|
||||
];
|
||||
let square_width = (self.dimensions[0] - 10) / 8;
|
||||
for l in 0..9 {
|
||||
instructions.extend([
|
||||
DrawInstructions::Rect([5 + square_width * l, 5], [2, self.dimensions[1] - 10], [0, 0, 0]),
|
||||
DrawInstructions::Rect([5, 5 + square_width * l], [self.dimensions[0] - 10, 2], [0, 0, 0]),
|
||||
]);
|
||||
}
|
||||
instructions.extend([
|
||||
DrawInstructions::Circle([5 + square_width * 2, 5 + square_width * 2], 4, [0, 0, 0]),
|
||||
DrawInstructions::Circle([5 + square_width * 6, 5 + square_width * 2], 4, [0, 0, 0]),
|
||||
DrawInstructions::Circle([5 + square_width * 2, 5 + square_width * 6], 4, [0, 0, 0]),
|
||||
DrawInstructions::Circle([5 + square_width * 6, 5 + square_width * 6], 4, [0, 0, 0]),
|
||||
]);
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
let tile = &self.tiles[y][x];
|
||||
if tile == &Tile::Empty {
|
||||
//
|
||||
} else {
|
||||
instructions.push(DrawInstructions::Circle([x * square_width + square_width / 2 + 5, y * square_width + square_width / 2 + 5], square_width / 2 - 3, tile.to_color().unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
instructions
|
||||
}
|
||||
|
||||
//properties
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Reversi".to_string()
|
||||
}
|
||||
|
||||
fn subtype(&self) -> WindowLikeType {
|
||||
WindowLikeType::Window
|
||||
}
|
||||
|
||||
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
|
||||
[300, 300]
|
||||
}
|
||||
}
|
||||
|
||||
impl Reversi {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn new_tiles(&mut self) {
|
||||
self.tiles = Default::default();
|
||||
self.tiles[3][3] = Tile::White;
|
||||
self.tiles[4][3] = Tile::Black;
|
||||
self.tiles[3][4] = Tile::Black;
|
||||
self.tiles[4][4] = Tile::White;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
listen(Reversi::new());
|
||||
}
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
use std::boxed::Box;
|
||||
|
||||
use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
|
||||
use ming_wm::framebuffer::Dimensions;
|
||||
use ming_wm::themes::ThemeInfo;
|
||||
use ming_wm::components::Component;
|
||||
use ming_wm::components::highlight_button::HighlightButton;
|
||||
use ming_wm::ipc::listen;
|
||||
|
||||
//todo: move to essential
|
||||
|
||||
static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "System", "Misc", "Help", "Logout"];
|
||||
|
||||
#[derive(Clone)]
|
||||
enum StartMenuMessage {
|
||||
CategoryClick(&'static str),
|
||||
WindowClick(&'static str),
|
||||
Back,
|
||||
ChangeAcknowledge,
|
||||
}
|
||||
|
||||
pub struct StartMenu {
|
||||
dimensions: Dimensions,
|
||||
components: Vec<Box<dyn Component<StartMenuMessage> + Send>>,
|
||||
current_focus: String,
|
||||
old_focus: String,
|
||||
y_each: usize,
|
||||
}
|
||||
|
||||
impl WindowLike for StartMenu {
|
||||
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
|
||||
match message {
|
||||
WindowMessage::Init(dimensions) => {
|
||||
self.dimensions = dimensions;
|
||||
self.y_each = (self.dimensions[1] - 1) / CATEGORIES.len();
|
||||
self.add_category_components();
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
WindowMessage::KeyPress(key_press) => {
|
||||
//up and down
|
||||
if key_press.key == 'k' || key_press.key == 'j' {
|
||||
let old_focus_index = self.get_focus_index().unwrap();
|
||||
self.components[old_focus_index].handle_message(WindowMessage::Unfocus);
|
||||
let current_focus_index = if key_press.key == 'j' {
|
||||
if old_focus_index + 1 == self.components.len() {
|
||||
0
|
||||
} else {
|
||||
old_focus_index + 1
|
||||
}
|
||||
} else {
|
||||
if old_focus_index == 0 {
|
||||
self.components.len() - 1
|
||||
} else {
|
||||
old_focus_index - 1
|
||||
}
|
||||
};
|
||||
self.old_focus = self.current_focus.to_string();
|
||||
self.current_focus = self.components[current_focus_index].name().to_string();
|
||||
self.components[current_focus_index].handle_message(WindowMessage::Focus);
|
||||
WindowMessageResponse::JustRerender
|
||||
} else if key_press.key == '𐘂' { //the enter key
|
||||
let focus_index = self.get_focus_index();
|
||||
if let Some(focus_index) = focus_index {
|
||||
let r = self.components[focus_index].handle_message(WindowMessage::FocusClick);
|
||||
self.handle_start_menu_message(r)
|
||||
} else {
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
} else {
|
||||
let current_focus_index = self.get_focus_index().unwrap();
|
||||
if let Some(n_index) = self.components[current_focus_index..].iter().position(|c| c.name().chars().next().unwrap_or('𐘂').to_lowercase().next().unwrap() == key_press.key) {
|
||||
//now old focus, not current focus
|
||||
self.components[current_focus_index].handle_message(WindowMessage::Unfocus);
|
||||
self.old_focus = self.current_focus.clone();
|
||||
self.current_focus = self.components[current_focus_index + n_index].name().to_string();
|
||||
self.components[current_focus_index + n_index].handle_message(WindowMessage::Focus);
|
||||
WindowMessageResponse::JustRerender
|
||||
} else {
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => WindowMessageResponse::DoNothing,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||
let mut instructions = vec![
|
||||
//top thin border
|
||||
DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top),
|
||||
//right thin border
|
||||
DrawInstructions::Rect([self.dimensions[0] - 1, 0], [1, self.dimensions[1]], theme_info.border_right_bottom),
|
||||
//background
|
||||
DrawInstructions::Rect([0, 1], [self.dimensions[0] - 1, self.dimensions[1] - 1], theme_info.background),
|
||||
//mingde logo
|
||||
DrawInstructions::Mingde([2, 2]),
|
||||
//I truly don't know why, it should be - 44 but - 30 seems to work better :shrug:
|
||||
DrawInstructions::Gradient([2, 42], [40, self.dimensions[1] - 30], [255, 201, 14], [225, 219, 77], 15),
|
||||
];
|
||||
for component in &self.components {
|
||||
instructions.extend(component.draw(theme_info));
|
||||
}
|
||||
instructions
|
||||
}
|
||||
|
||||
//properties
|
||||
fn subtype(&self) -> WindowLikeType {
|
||||
WindowLikeType::StartMenu
|
||||
}
|
||||
|
||||
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
|
||||
[175, 250]
|
||||
}
|
||||
}
|
||||
|
||||
impl StartMenu {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
dimensions: [0, 0],
|
||||
components: Vec::new(),
|
||||
current_focus: String::new(), //placeholder, will be set in init
|
||||
old_focus: String::new(),
|
||||
y_each: 0, //will be set in add_category_components
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_start_menu_message(&mut self, message: Option<StartMenuMessage>) -> WindowMessageResponse {
|
||||
if let Some(message) = message {
|
||||
match message {
|
||||
StartMenuMessage::CategoryClick(name) => {
|
||||
if name == "Logout" {
|
||||
WindowMessageResponse::Request(WindowManagerRequest::Lock)
|
||||
} else {
|
||||
self.current_focus = "Back".to_string();
|
||||
self.components = vec![
|
||||
Box::new(HighlightButton::new(
|
||||
"Back".to_string(), [42, 1], [self.dimensions[0] - 42 - 1, self.y_each], "Back", StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true
|
||||
))
|
||||
];
|
||||
//add window buttons
|
||||
let mut to_add: Vec<&str> = Vec::new();
|
||||
if name == "Games" {
|
||||
to_add.push("Minesweeper");
|
||||
} else if name == "Editing" {
|
||||
to_add.push("Malvim");
|
||||
} else if name == "Utils" {
|
||||
to_add.push("Terminal");
|
||||
} else if name == "Files" {
|
||||
to_add.push("Audio Player");
|
||||
}
|
||||
//
|
||||
for a in 0..to_add.len() {
|
||||
let w_name = to_add[a];
|
||||
self.components.push(Box::new(HighlightButton::new(
|
||||
w_name.to_string(), [42, (a + 1) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], w_name, StartMenuMessage::WindowClick(w_name), StartMenuMessage::ChangeAcknowledge, false
|
||||
)));
|
||||
}
|
||||
WindowMessageResponse::JustRerender
|
||||
}
|
||||
},
|
||||
StartMenuMessage::WindowClick(name) => {
|
||||
//open the selected window
|
||||
WindowMessageResponse::Request(WindowManagerRequest::OpenWindow(name.to_string()))
|
||||
},
|
||||
StartMenuMessage::Back => {
|
||||
self.add_category_components();
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
StartMenuMessage::ChangeAcknowledge => {
|
||||
//
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
}
|
||||
} else {
|
||||
//maybe should be JustRerender?
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_category_components(&mut self) {
|
||||
self.current_focus = "About".to_string();
|
||||
self.components = Vec::new();
|
||||
for c in 0..CATEGORIES.len() {
|
||||
let name = CATEGORIES[c];
|
||||
self.components.push(Box::new(HighlightButton::new(
|
||||
name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name, StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_focus_index(&self) -> Option<usize> {
|
||||
self.components.iter().filter(|c| c.focusable()).position(|c| c.name() == &self.current_focus)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
listen(StartMenu::new());
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::vec::Vec;
|
||||
use std::vec;
|
||||
use std::process::{ Command, Output };
|
||||
use std::str::from_utf8;
|
||||
use std::io;
|
||||
use std::process::{ Command, Child, Stdio };
|
||||
use std::io::Read;
|
||||
|
||||
use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||
use ming_wm::messages::{ WindowMessage, WindowMessageResponse };
|
||||
@@ -15,19 +14,23 @@ const MONO_WIDTH: u8 = 10;
|
||||
const LINE_HEIGHT: usize = 15;
|
||||
const PADDING: usize = 4;
|
||||
|
||||
enum CommandResponse {
|
||||
ActualCommand(io::Result<Output>),
|
||||
Custom,
|
||||
#[derive(Default, PartialEq)]
|
||||
enum State {
|
||||
#[default]
|
||||
Input, //typing in to run command
|
||||
Running, //running command
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Terminal {
|
||||
dimensions: Dimensions,
|
||||
state: State,
|
||||
lines: Vec<String>,
|
||||
actual_lines: Vec<String>, //wrapping
|
||||
actual_line_num: usize, //what line # is at the top, for scrolling
|
||||
current_input: String,
|
||||
current_path: String,
|
||||
running_process: Option<Child>,
|
||||
}
|
||||
|
||||
//for some reason key presses, then moving the window leaves the old window still there, behind it. weird
|
||||
@@ -47,35 +50,55 @@ impl WindowLike for Terminal {
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
WindowMessage::KeyPress(key_press) => {
|
||||
if key_press.key == '𐘁' { //backspace
|
||||
if self.current_input.len() > 0 {
|
||||
self.current_input = self.current_input[..self.current_input.len() - 1].to_string();
|
||||
} else {
|
||||
return WindowMessageResponse::DoNothing;
|
||||
}
|
||||
} else if key_press.key == '𐘂' { //the enter key
|
||||
self.lines.push("$ ".to_string() + &self.current_input);
|
||||
if let CommandResponse::ActualCommand(maybe_output) = self.process_command() {
|
||||
if let Ok(output) = maybe_output {
|
||||
let write_output = if output.status.success() {
|
||||
output.stdout
|
||||
} else {
|
||||
output.stderr
|
||||
};
|
||||
for line in from_utf8(&write_output).unwrap_or("Failed to parse process output as utf-8").split("\n") {
|
||||
self.lines.push(line.to_string());
|
||||
}
|
||||
if self.state == State::Input {
|
||||
if key_press.key == '𐘁' { //backspace
|
||||
if self.current_input.len() > 0 {
|
||||
self.current_input = self.current_input[..self.current_input.len() - 1].to_string();
|
||||
} else {
|
||||
self.lines.push("Failed to execute process".to_string());
|
||||
return WindowMessageResponse::DoNothing;
|
||||
}
|
||||
} else if key_press.key == '𐘂' { //the enter key
|
||||
self.lines.push("$ ".to_string() + &self.current_input);
|
||||
self.state = self.process_command();
|
||||
self.current_input = String::new();
|
||||
} else {
|
||||
self.current_input += &key_press.key.to_string();
|
||||
}
|
||||
self.current_input = String::new();
|
||||
self.calc_actual_lines();
|
||||
self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0);
|
||||
WindowMessageResponse::JustRerender
|
||||
} else {
|
||||
self.current_input += &key_press.key.to_string();
|
||||
//update
|
||||
let running_process = self.running_process.as_mut().unwrap();
|
||||
if let Some(status) = running_process.try_wait().unwrap() {
|
||||
//process exited
|
||||
let mut output = String::new();
|
||||
if status.success() {
|
||||
let _ = running_process.stdout.as_mut().unwrap().read_to_string(&mut output);
|
||||
} else {
|
||||
let _ = running_process.stderr.as_mut().unwrap().read_to_string(&mut output);
|
||||
}
|
||||
for line in output.split("\n") {
|
||||
self.lines.push(line.to_string());
|
||||
}
|
||||
self.state = State::Input;
|
||||
self.calc_actual_lines();
|
||||
WindowMessageResponse::JustRerender
|
||||
} else {
|
||||
//still running
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
}
|
||||
},
|
||||
WindowMessage::CtrlKeyPress(key_press) => {
|
||||
if self.state == State::Running && key_press.key == 'c' {
|
||||
//kills and running_process is now None
|
||||
let _ = self.running_process.take().unwrap().kill();
|
||||
self.state = State::Input;
|
||||
WindowMessageResponse::JustRerender
|
||||
} else {
|
||||
WindowMessageResponse::DoNothing
|
||||
}
|
||||
self.calc_actual_lines();
|
||||
self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0);
|
||||
WindowMessageResponse::JustRerender
|
||||
},
|
||||
_ => WindowMessageResponse::DoNothing,
|
||||
}
|
||||
@@ -125,10 +148,10 @@ impl Terminal {
|
||||
(self.dimensions[1] - PADDING * 2) / LINE_HEIGHT
|
||||
}
|
||||
|
||||
fn process_command(&mut self) -> CommandResponse {
|
||||
fn process_command(&mut self) -> State {
|
||||
if self.current_input.starts_with("clear ") || self.current_input == "clear" {
|
||||
self.lines = Vec::new();
|
||||
CommandResponse::Custom
|
||||
State::Input
|
||||
} else if self.current_input.starts_with("cd ") {
|
||||
let mut cd_split = self.current_input.split(" ");
|
||||
cd_split.next().unwrap();
|
||||
@@ -138,16 +161,22 @@ impl Terminal {
|
||||
self.current_path = new_path.to_str().unwrap().to_string();
|
||||
}
|
||||
}
|
||||
CommandResponse::Custom
|
||||
State::Input
|
||||
} else {
|
||||
CommandResponse::ActualCommand(Command::new("sh").arg("-c").arg(&self.current_input).current_dir(&self.current_path).output())
|
||||
self.running_process = Some(Command::new("sh").arg("-c").arg(&self.current_input).current_dir(&self.current_path).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap());
|
||||
State::Running
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_actual_lines(&mut self) {
|
||||
self.actual_lines = Vec::new();
|
||||
let max_chars_per_line = (self.dimensions[0] - PADDING * 2) / MONO_WIDTH as usize;
|
||||
for line_num in 0..=self.lines.len() {
|
||||
let end = if self.state == State::Input {
|
||||
self.lines.len()
|
||||
} else {
|
||||
self.lines.len() - 1
|
||||
};
|
||||
for line_num in 0..=end {
|
||||
let mut working_line = if line_num == self.lines.len() {
|
||||
"$ ".to_string() + &self.current_input + "█"
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::process::{ Command, Stdio };
|
||||
use std::io::{ Read, Write };
|
||||
|
||||
use ron;
|
||||
|
||||
fn main() {
|
||||
println!("a");
|
||||
let mut a = Command::new("cargo").arg("run").arg("-q").arg("--bin").arg("start_menu").stdout(Stdio::piped()).stdin(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
|
||||
@@ -8,4 +10,5 @@ fn main() {
|
||||
let mut output = String::new();
|
||||
a.stdout.as_mut().unwrap().read_to_string(&mut output);
|
||||
println!("{}", output);
|
||||
//println!("{}", &ron::to_string(&[122, 400]).unwrap());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user