handle window crashes, various fixes

add reversi, fixes for audio player and terminal
This commit is contained in:
stjet
2024-10-28 05:07:35 +00:00
parent cfece80c66
commit 4be9bbc411
15 changed files with 319 additions and 93 deletions

View File

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

View File

@@ -69,7 +69,7 @@ struct Current {
}
#[derive(Default)]
pub struct Malvim {
struct Malvim {
dimensions: Dimensions,
state: State,
mode: Mode,

View File

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

View File

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

View File

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

View File

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