diff --git a/src/bin/audio_player.rs b/src/bin/audio_player.rs index 711fd42..b092459 100644 --- a/src/bin/audio_player.rs +++ b/src/bin/audio_player.rs @@ -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)) }; diff --git a/src/bin/malvim.rs b/src/bin/malvim.rs index 6c4f5a4..3e57adc 100644 --- a/src/bin/malvim.rs +++ b/src/bin/malvim.rs @@ -69,7 +69,7 @@ struct Current { } #[derive(Default)] -pub struct Malvim { +struct Malvim { dimensions: Dimensions, state: State, mode: Mode, diff --git a/src/bin/minesweeper.rs b/src/bin/minesweeper.rs index c81c036..e06d85d 100644 --- a/src/bin/minesweeper.rs +++ b/src/bin/minesweeper.rs @@ -42,7 +42,7 @@ enum State { } #[derive(Default)] -pub struct Minesweeper { +struct Minesweeper { dimensions: Dimensions, state: State, tiles: [[MineTile; 16]; 16], diff --git a/src/bin/reversi.rs b/src/bin/reversi.rs new file mode 100644 index 0000000..711f627 --- /dev/null +++ b/src/bin/reversi.rs @@ -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 { + 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 { + 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()); +} + diff --git a/src/bin/terminal.rs b/src/bin/terminal.rs index d79d435..27901a4 100644 --- a/src/bin/terminal.rs +++ b/src/bin/terminal.rs @@ -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), - 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, actual_lines: Vec, //wrapping actual_line_num: usize, //what line # is at the top, for scrolling current_input: String, current_path: String, + running_process: Option, } //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 { diff --git a/src/bin/test.rs b/src/bin/test.rs index 3ec9335..d607eb5 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -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()); } diff --git a/src/essential/mod.rs b/src/essential/mod.rs index b80c7a5..39a401d 100644 --- a/src/essential/mod.rs +++ b/src/essential/mod.rs @@ -2,4 +2,5 @@ pub mod desktop_background; pub mod taskbar; pub mod lock_screen; pub mod workspace_indicator; +pub mod start_menu; diff --git a/src/bin/start_menu.rs b/src/essential/start_menu.rs similarity index 94% rename from src/bin/start_menu.rs rename to src/essential/start_menu.rs index c6edb80..e1b8ba4 100644 --- a/src/bin/start_menu.rs +++ b/src/essential/start_menu.rs @@ -4,13 +4,13 @@ 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; +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::highlight_button::HighlightButton; +use crate::ipc::listen; //todo: move to essential @@ -145,7 +145,7 @@ impl StartMenu { //add window buttons let mut to_add: Vec<&str> = Vec::new(); if name == "Games" { - to_add.push("Minesweeper"); + to_add.extend(["Minesweeper", "Reversi"]); } else if name == "Editing" { to_add.push("Malvim"); } else if name == "Utils" { diff --git a/src/essential/workspace_indicator.rs b/src/essential/workspace_indicator.rs index 3c61d82..1a6c08c 100644 --- a/src/essential/workspace_indicator.rs +++ b/src/essential/workspace_indicator.rs @@ -1,5 +1,6 @@ use std::vec; use std::vec::Vec; +use std::time::{ SystemTime, UNIX_EPOCH }; use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, INDICATOR_HEIGHT }; use crate::messages::{ WindowMessage, WindowMessageResponse, ShortcutType }; @@ -7,6 +8,9 @@ use crate::framebuffer::Dimensions; use crate::themes::ThemeInfo; const WIDTH: usize = 15; +const ONE_MINUTE: u64 = 60; +const ONE_HOUR: u64 = 60 * ONE_MINUTE; +const ONE_DAY: u64 = 24 * ONE_HOUR; pub struct WorkspaceIndicator { dimensions: Dimensions, @@ -49,6 +53,12 @@ impl WindowLike for WorkspaceIndicator { instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman".to_string(), (w + 1).to_string(), theme_info.text, theme_info.background, None, None)); } } + //also add the utc time in the right edge + let today_secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() % ONE_DAY; + let hours = (today_secs / ONE_HOUR).to_string(); + let minutes = ((today_secs % ONE_HOUR) / ONE_MINUTE).to_string(); + let time_string = format!("{}:{}~ UTC", if hours.len() == 1 { "0".to_string() + &hours } else { hours }, if minutes.len() == 1 { "0".to_string() + &minutes } else { minutes }); + instructions.push(DrawInstructions::Text([self.dimensions[0] - 90, 4], "times-new-roman".to_string(), time_string, theme_info.text, theme_info.background, None, None)); instructions } diff --git a/src/framebuffer.rs b/src/framebuffer.rs index e3692ad..b9d44f7 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -17,12 +17,16 @@ fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor (bg_color[2] as f32 * (1.0 - factor)) as u8 + (color[2] as f32 * factor) as u8, ]*/ //255 * 255 < max(u16) - let alpha = alpha as u16; - [ - (bg_color[0] as u16 * (255 - alpha) / 255) as u8 + (color[0] as u16 * alpha / 255) as u8, - (bg_color[1] as u16 * (255 - alpha) / 255) as u8 + (color[1] as u16 * alpha / 255) as u8, - (bg_color[2] as u16 * (255 - alpha) / 255) as u8 + (color[2] as u16 * alpha / 255) as u8, - ] + if alpha == 255 { + color + } else { + let alpha = alpha as u16; + [ + (bg_color[0] as u16 * (255 - alpha) / 255) as u8 + (color[0] as u16 * alpha / 255) as u8, + (bg_color[1] as u16 * (255 - alpha) / 255) as u8 + (color[1] as u16 * alpha / 255) as u8, + (bg_color[2] as u16 * (255 - alpha) / 255) as u8 + (color[2] as u16 * alpha / 255) as u8, + ] + } } #[derive(Clone, Default, Debug)] @@ -126,6 +130,21 @@ impl FramebufferWriter { } } + //can optimise (?) by turning into lines and doing _draw_line instead? + pub fn draw_circle(&mut self, centre: Point, radius: usize, color: RGBColor) { + //x^2 + y^2 <= r^2 + for y in 0..radius { + for x in 0..radius { + if (x.pow(2) + y.pow(2)) <= radius.pow(2) { + self.draw_pixel([centre[0] + x, centre[1] + y], color); + self.draw_pixel([centre[0] - x, centre[1] + y], color); + self.draw_pixel([centre[0] - x, centre[1] - y], color); + self.draw_pixel([centre[0] + x, centre[1] - y], color); + } + } + } + } + //direction is top to bottom pub fn draw_gradient(&mut self, top_left: Point, dimensions: Dimensions, start_color: RGBColor, end_color: RGBColor, steps: usize) { let delta_r = (end_color[0] as f32 - start_color[0] as f32) / steps as f32; @@ -159,6 +178,7 @@ impl FramebufferWriter { //text + //this, draw_char, and get_font_char should be more much optimised pub fn draw_text(&mut self, top_left: Point, font_name: &str, text: &str, color: RGBColor, bg_color: RGBColor, horiz_spacing: usize, mono_width: Option) { let mut top_left = top_left; //todo, config space diff --git a/src/ipc.rs b/src/ipc.rs index 0171415..2716bc0 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -44,7 +44,7 @@ pub fn listen(mut window_like: impl WindowLike) { } else if method == "subtype" { println!("{}", ron::to_string(&window_like.subtype()).unwrap()); } else if method == "ideal_dimensions" { - println!("{:?}", window_like.ideal_dimensions(ron::from_str(arg).unwrap())); + println!("{}", ron::to_string(&window_like.ideal_dimensions(ron::from_str(arg).unwrap())).unwrap()); } } } diff --git a/src/keyboard.rs b/src/keyboard.rs index 1dcedb7..6834d76 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -4,6 +4,7 @@ use termion::event::Key; pub enum KeyChar { Press(char), Alt(char), + Ctrl(char), } //use Linear A for escape, backspace, enter @@ -12,6 +13,7 @@ pub fn key_to_char(key: Key) -> Option { Key::Char('\n') => Some(KeyChar::Press('𐘂')), Key::Char(c) => Some(KeyChar::Press(c)), Key::Alt(c) => Some(KeyChar::Alt(c)), + Key::Ctrl(c) => Some(KeyChar::Ctrl(c)), Key::Backspace => Some(KeyChar::Press('𐘁')), Key::Esc => Some(KeyChar::Press('𐘃')), _ => None, diff --git a/src/messages.rs b/src/messages.rs index 7d77e5c..784d970 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -49,7 +49,6 @@ pub enum WindowMessageResponse { #[derive(Serialize, Deserialize)] pub struct KeyPress { pub key: char, - // } #[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] @@ -90,6 +89,7 @@ pub enum InfoType { pub enum WindowMessage { Init(Dimensions), KeyPress(KeyPress), + CtrlKeyPress(KeyPress), Shortcut(ShortcutType), Info(InfoType), Focus, diff --git a/src/proxy_window_like.rs b/src/proxy_window_like.rs index aa51eb2..e44b3fd 100644 --- a/src/proxy_window_like.rs +++ b/src/proxy_window_like.rs @@ -1,6 +1,6 @@ use std::vec::Vec; use std::process::{ Command, Child, Stdio }; -use std::io::{ BufReader, BufRead, Read, Write }; +use std::io::{ BufReader, BufRead, Write }; use std::cell::RefCell; use ron; @@ -14,66 +14,91 @@ pub struct ProxyWindowLike { process: RefCell, } +//try to handle panics of child processes so the entire wm doesn't crash +//we can "guarantee" that the ron::to_string(...).unwrap() calls will never panic impl WindowLike for ProxyWindowLike { fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all(("handle_message ".to_string() + &ron::to_string(&message).unwrap() + "\n").as_bytes()); + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all(("handle_message ".to_string() + &ron::to_string(&message).unwrap() + "\n").as_bytes()); + } let output = self.read_line(); - ron::from_str(&output).unwrap() + ron::from_str(&output).unwrap_or(WindowMessageResponse::JustRerender) } fn draw(&self, theme_info: &ThemeInfo) -> Vec { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all(("draw ".to_string() + &ron::to_string(&theme_info).unwrap() + "\n").as_bytes()); + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all(("draw ".to_string() + &ron::to_string(&theme_info).unwrap() + "\n").as_bytes()); + } let output = self.read_line(); - ron::from_str(&output).unwrap() + ron::from_str(&output).unwrap_or(Vec::new()) } //properties fn title(&self) -> String { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all("title\n".as_bytes()); - self.read_line() + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all("title\n".as_bytes()); + } + self.read_line().chars().filter(|c| *c != '\n').collect() } fn resizable(&self) -> bool { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all("resizable\n".to_string().as_bytes()); + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all("resizable\n".to_string().as_bytes()); + } let output = self.read_line(); - ron::from_str(&output).unwrap() + ron::from_str(&output).unwrap_or(false) } fn subtype(&self) -> WindowLikeType { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all("subtype\n".to_string().as_bytes()); + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all("subtype\n".to_string().as_bytes()); + } let output = self.read_line(); - ron::from_str(&output).unwrap() + ron::from_str(&output).unwrap_or(WindowLikeType::Window) } fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { - self.process.borrow_mut().stdin.as_mut().unwrap().write_all(("ideal_dimensions".to_string() + &ron::to_string(&dimensions).unwrap() + "\n").as_bytes()); + if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { + let _ = stdin.write_all(("ideal_dimensions ".to_string() + &ron::to_string(&dimensions).unwrap() + "\n").as_bytes()); + } let output = self.read_line(); - ron::from_str(&output).unwrap() + ron::from_str(&output).unwrap_or([420, 420]) } } //kill process when this window like dropped impl Drop for ProxyWindowLike { fn drop(&mut self) { - self.process.borrow_mut().kill(); + let _ = self.process.borrow_mut().kill(); } } impl ProxyWindowLike { - pub fn new(file: &str) -> Self { + pub fn new(command: &mut Command) -> Self { ProxyWindowLike { - //--quiet - process: RefCell::new(Command::new("cargo").arg("run").arg("--quiet").arg("--release").arg("--bin").arg(file).stdout(Stdio::piped()).stdin(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap()), + process: RefCell::new(command.stdout(Stdio::piped()).stdin(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap()), } } + pub fn new_rust(file: &str) -> Self { + ProxyWindowLike::new(Command::new("cargo").arg("run").arg("--quiet").arg("--release").arg("--bin").arg(file).stdout(Stdio::piped()).stdin(Stdio::piped()).stderr(Stdio::null())) + } + + //return empty string if error, do not propogate Err becuase that's messy + //or maybe return "panicked"? fn read_line(&self) -> String { - let mut output = String::new(); let mut buffer = self.process.borrow_mut(); - let buffer = buffer.stdout.as_mut().unwrap(); - let mut reader = BufReader::new(buffer); - reader.read_line(&mut output).unwrap(); - output + if let Some(buffer) = buffer.stdout.as_mut() { + let mut output = String::new(); + let mut reader = BufReader::new(buffer); + if let Ok(_) = reader.read_line(&mut output) { + output + } else { + String::new() + } + } else { + String::new() + } } } diff --git a/src/window_manager.rs b/src/window_manager.rs index a0b95e1..f010762 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -22,8 +22,7 @@ use crate::essential::desktop_background::DesktopBackground; use crate::essential::taskbar::Taskbar; use crate::essential::lock_screen::LockScreen; use crate::essential::workspace_indicator::WorkspaceIndicator; - -//todo, better error handling for windows +use crate::essential::start_menu::StartMenu; pub const TASKBAR_HEIGHT: usize = 38; pub const INDICATOR_HEIGHT: usize = 20; @@ -46,6 +45,7 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { write!(stdout, "{}", cursor::Hide).unwrap(); stdout.flush().unwrap(); + for c in stdin.keys() { if let Some(kc) = key_to_char(c.unwrap()) { //do not allow exit when locked unless debugging @@ -73,6 +73,7 @@ pub enum DrawInstructions { Text(Point, String, String, RGBColor, RGBColor, Option, Option), //font and text Gradient(Point, Dimensions, RGBColor, RGBColor, usize), Mingde(Point), + Circle(Point, usize, RGBColor), } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -462,13 +463,19 @@ impl WindowManager { } press_response }, - KeyChar::Press(c) => { + KeyChar::Press(c) | KeyChar::Ctrl(c) => { let mut press_response = WindowMessageResponse::DoNothing; //send to focused window if let Some(focused_index) = self.get_focused_index() { - press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::KeyPress(KeyPress { - key: c, - })); + press_response = self.window_infos[focused_index].window_like.handle_message(if key_char == KeyChar::Press(c) { + WindowMessage::KeyPress(KeyPress { + key: c, + }) + } else { + WindowMessage::CtrlKeyPress(KeyPress { + key: c, + }) + }); //at most, only the focused window needs to be rerendered redraw_ids = Some(vec![self.window_infos[focused_index].id]); //requests can result in window openings and closings, etc @@ -499,14 +506,19 @@ impl WindowManager { if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { return; } - let w: WindowBox = match w.as_str() { - "Minesweeper" => Box::new(ProxyWindowLike::new("minesweeper")), - "Malvim" => Box::new(ProxyWindowLike::new("malvim")), - "Terminal" => Box::new(ProxyWindowLike::new("terminal")), - "Audio Player" => Box::new(ProxyWindowLike::new("audio_player")), - "StartMenu" => Box::new(ProxyWindowLike::new("start_menu")), - _ => panic!("window not found"), //todo: do not panic + let w: Option = match w.as_str() { + "Minesweeper" => Some(Box::new(ProxyWindowLike::new_rust("minesweeper"))), + "Reversi" => Some(Box::new(ProxyWindowLike::new_rust("reversi"))), + "Malvim" => Some(Box::new(ProxyWindowLike::new_rust("malvim"))), + "Terminal" => Some(Box::new(ProxyWindowLike::new_rust("terminal"))), + "Audio Player" => Some(Box::new(ProxyWindowLike::new_rust("audio_player"))), + "StartMenu" => Some(Box::new(StartMenu::new())), + _ => None, }; + if w.is_none() { + return; + } + let w = w.unwrap(); //close start menu if open self.toggle_start_menu(true); let ideal_dimensions = w.ideal_dimensions(self.dimensions); @@ -587,6 +599,7 @@ impl WindowManager { instructions = instructions.iter().map(|instruction| { match instruction { DrawInstructions::Rect(top_left, dimensions, color) => DrawInstructions::Rect(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *color), + DrawInstructions::Circle(centre, radius, color) => DrawInstructions::Circle(WindowManager::get_true_top_left(centre, is_window), *radius, *color), DrawInstructions::Text(top_left, font_name, text, color, bg_color, horiz_spacing, mono_width) => DrawInstructions::Text(WindowManager::get_true_top_left(top_left, is_window), font_name.clone(), text.clone(), *color, *bg_color, *horiz_spacing, *mono_width), DrawInstructions::Mingde(top_left) => DrawInstructions::Mingde(WindowManager::get_true_top_left(top_left, is_window)), DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => DrawInstructions::Gradient(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *start_color, *end_color, *steps), @@ -630,6 +643,9 @@ impl WindowManager { ]; window_writer.draw_rect(top_left, true_dimensions, color); }, + DrawInstructions::Circle(centre, radius, color) => { + window_writer.draw_circle(centre, radius, color); + }, DrawInstructions::Text(top_left, font_name, text, color, bg_color, horiz_spacing, mono_width) => { window_writer.draw_text(top_left, &font_name, &text, color, bg_color, horiz_spacing.unwrap_or(1), mono_width); }, @@ -643,7 +659,6 @@ impl WindowManager { } WRITER.lock().unwrap().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer()); w_index += 1; - //core::mem::drop(temp_vec); } self.framebuffer.write_frame(WRITER.lock().unwrap().get_buffer()); }