diff --git a/README.md b/README.md index 6ea3dcd..6b4ee8f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is single-threaded, and is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to the previous [mingde](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os). ![example 1](/docs/images/ws1.png) -![example 2](/docs/images/ws2.png) +![example 2](/docs/images/ws3.png) ## Running diff --git a/docs/system/README.md b/docs/system/README.md index dbff55d..affcde3 100644 --- a/docs/system/README.md +++ b/docs/system/README.md @@ -49,13 +49,13 @@ The event loop goes like this: 1. Keyboard event received, sent to the window manager 2. The window manager interprets it. It could be a shortcut to say, open the start meny. Or, if a window-like is currently focused, the window manager will probably forward the keyboard event to that window-like by calling the window-like's `handle_message` method 3. (Only if sent to a window-like) The window-like receives and processes it. It returns a `WindowMessageResponse`: ->>> ```rust -pub enum WindowMessageResponse { - Request(WindowManagerRequest), - JustRerender, - DoNothing, -} -``` +> ```rust +> pub enum WindowMessageResponse { +> Request(WindowManagerRequest), +> JustRerender, +> DoNothing, +> } +> ``` 4. If the window manager decides the keyboard event means some or all of the screen needs to be redrawn (eg, it was a valid shortcut, or the window-like it sent the event to returned something that wasn't a `DoNothing`), it will go and get the drawing instructions from all the window-likes that need to be redrawn by looping through them and calling their `draw` method Nothing except key presses trigger redraws. That means no mouse and no animations. This is a positive. This is a positive. I truly believe that. This is a positive. Having a window manager and windows that don't require taking hands off the keyboard (or rather, entirely designed to be keyboard operated) makes using it very fast and efficient, with no pain of not having a mice and needing to use a shitty mousepad. Videos are nice, but animations and the like are annoying and have no place in a good window manager. diff --git a/docs/window-likes/audio-player.md b/docs/window-likes/audio-player.md new file mode 100644 index 0000000..8e1ea32 --- /dev/null +++ b/docs/window-likes/audio-player.md @@ -0,0 +1,23 @@ +Audio player with playlist and folder support. + +## Commands + +Type to write commands, backspace to delete last character, and enter to run command. + +- `t`: Toggle pause/play +- `l`: Next/skip +- `j`: Volume down +- `k`: Volume up +- `b `: Set base directory (`` is path) +- `p `: Play audio files in `` or play the songs listed in the ``. Unless paths are absolute, they will be relative to the directory specified by the `b ` command + +## Playlists + +Example playlist file: + +``` +hanyuu-maigo/オノマトペ +inabakumori/* +iyowa/* +kai/さよならプリンセス +``` diff --git a/src/bin/audio_player.rs b/src/bin/audio_player.rs index 8899b00..876b20b 100644 --- a/src/bin/audio_player.rs +++ b/src/bin/audio_player.rs @@ -12,7 +12,7 @@ use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use ming_wm::messages::{ WindowMessage, WindowMessageResponse }; use ming_wm::framebuffer::Dimensions; use ming_wm::themes::ThemeInfo; -use ming_wm::utils::{ concat_paths, format_seconds }; +use ming_wm::utils::{ concat_paths, format_seconds, Substring }; use ming_wm::fs::get_all_files; use ming_wm::ipc::listen; @@ -47,7 +47,7 @@ impl WindowLike for AudioPlayer { 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(); + self.command = self.command.remove_last(); } } else { self.command += &key_press.key.to_string(); @@ -74,6 +74,9 @@ impl WindowLike for AudioPlayer { 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 + 2], vec!["times-new-romono".to_string()], time_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH))); } + } else { + instructions.push(DrawInstructions::Text([2, 2], vec!["times-new-roman".to_string()], "type to write commands, enter to execute.".to_string(), theme_info.text, theme_info.background, None, None)); + instructions.push(DrawInstructions::Text([2, 2 + LINE_HEIGHT], vec!["times-new-roman".to_string()], "See help in start menu for commands.".to_string(), theme_info.text, theme_info.background, None, None)); } // instructions diff --git a/src/bin/file_explorer.rs b/src/bin/file_explorer.rs index a90c934..2e98da2 100644 --- a/src/bin/file_explorer.rs +++ b/src/bin/file_explorer.rs @@ -1,6 +1,6 @@ use std::vec::Vec; use std::vec; -use std::fs::read_dir; +use std::fs::{ read_dir, metadata, Metadata }; use std::path::PathBuf; use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; @@ -18,6 +18,13 @@ struct DirectoryChild { is_file: bool, } +#[derive(Default, PartialEq)] +enum State { + #[default] + List, + Info, +} + #[derive(Default)] pub struct FileExplorer { dimensions: Dimensions, @@ -26,6 +33,8 @@ pub struct FileExplorer { //for scrolling and selecting dirs position: usize, top_position: usize, + state: State, + metadata: Option, } impl WindowLike for FileExplorer { @@ -42,6 +51,7 @@ impl WindowLike for FileExplorer { WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { + self.state = State::List; if key_press.key == '𐘂' { //the enter key if self.current_dir_contents.len() > 0 { let selected_entry = &self.current_dir_contents[self.position]; @@ -73,14 +83,20 @@ impl WindowLike for FileExplorer { //calculate position let max_height = self.dimensions[1] - HEIGHT; if self.position > self.top_position { - let current_height = (self.position - self.top_position) * HEIGHT; + let current_height = (self.position - self.top_position + 1) * HEIGHT; if current_height > self.dimensions[1] { - self.top_position += (current_height - max_height) / HEIGHT + 1; + //somehow this is slightly off sometimes + self.top_position += (current_height - max_height).div_ceil(HEIGHT); } } else { self.top_position = self.position; }; WindowMessageResponse::JustRedraw + } else if key_press.key == 'i' { + self.state = State::Info; + let selected_entry = &self.current_dir_contents[self.position]; + self.metadata = Some(metadata(&selected_entry.path).unwrap()); + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } @@ -91,29 +107,38 @@ impl WindowLike for FileExplorer { fn draw(&self, theme_info: &ThemeInfo) -> Vec { let mut instructions = Vec::new(); - //top bar with path name - instructions.push(DrawInstructions::Text([5, 0], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + &self.current_path.to_string_lossy().to_string(), theme_info.text, theme_info.background, None, None)); - //the actual files and directories - let mut start_y = HEIGHT; - let mut i = self.top_position; - for entry in self.current_dir_contents.iter().skip(self.top_position) { - if start_y > self.dimensions[1] { - break; + if self.state == State::List { + //top bar with path name + instructions.push(DrawInstructions::Text([5, 0], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + &self.current_path.to_string_lossy().to_string(), theme_info.text, theme_info.background, None, None)); + //the actual files and directories + let mut start_y = HEIGHT; + let mut i = self.top_position; + for entry in self.current_dir_contents.iter().skip(self.top_position) { + if start_y > self.dimensions[1] { + break; + } + let is_selected = i == self.position; + if is_selected { + instructions.push(DrawInstructions::Rect([0, start_y], [self.dimensions[0], HEIGHT], theme_info.top)); + } + //unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently + let name = entry.override_name.clone(); + let name = if name.is_none() { + entry.path.file_name().unwrap().to_os_string().into_string().unwrap() + } else { + name.unwrap() + }; + instructions.push(DrawInstructions::Text([5, start_y], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], name, if is_selected { theme_info.top_text } else { theme_info.text }, if is_selected { theme_info.top } else { theme_info.background }, None, None)); + start_y += HEIGHT; + i += 1; } - let is_selected = i == self.position; - if is_selected { - instructions.push(DrawInstructions::Rect([0, start_y], [self.dimensions[0], HEIGHT], theme_info.top)); - } - //unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently - let name = entry.override_name.clone(); - let name = if name.is_none() { - entry.path.file_name().unwrap().to_os_string().into_string().unwrap() - } else { - name.unwrap() - }; - instructions.push(DrawInstructions::Text([5, start_y], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], name, if is_selected { theme_info.top_text } else { theme_info.text }, if is_selected { theme_info.top } else { theme_info.background }, None, None)); + } else if self.state == State::Info { + let metadata = self.metadata.clone().unwrap(); + let mut start_y = HEIGHT; + let bytes_len = metadata.len(); + instructions.push(DrawInstructions::Text([5, start_y], vec!["times-new-roman".to_string()], format!("Size: {} mb ({} b)", bytes_len / (1024_u64).pow(2), bytes_len), theme_info.text, theme_info.background, None, None)); start_y += HEIGHT; - i += 1; + //todo: other stuff } instructions } @@ -165,4 +190,3 @@ impl FileExplorer { pub fn main() { listen(FileExplorer::new()); } - diff --git a/src/bin/reversi.rs b/src/bin/reversi.rs index a6dd067..05e7b42 100644 --- a/src/bin/reversi.rs +++ b/src/bin/reversi.rs @@ -43,6 +43,15 @@ impl Tile { } } +#[derive(Default, PartialEq)] +enum State { + #[default] + InProgress, + WhiteWin, + BlackWin, + Tie, +} + #[derive(Default)] struct Reversi { dimensions: Dimensions, @@ -50,6 +59,7 @@ struct Reversi { current_number: Option, //the first number of the tile that user wants to place piece on valid_moves: Vec, white_turn: bool, //if false, black turn + state: State, } impl WindowLike for Reversi { @@ -62,7 +72,11 @@ impl WindowLike for Reversi { WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { - if let Ok(n) = key_press.key.to_string().parse::() { + if self.state != State::InProgress { + self.state = State::InProgress; + self.new_tiles(); + self.valid_moves = self.get_valid_moves(); + } else if let Ok(n) = key_press.key.to_string().parse::() { if let Some(current_number) = self.current_number { let y = current_number as usize; let x = n as usize; @@ -74,6 +88,27 @@ impl WindowLike for Reversi { } self.white_turn = !self.white_turn; self.valid_moves = self.get_valid_moves(); + if self.valid_moves.len() == 0 { + //game has ended + let mut white_tiles = 0; + let mut black_tiles = 0; + for row in &self.tiles { + for tile in row { + if tile == &Tile::White { + white_tiles += 1; + } else if tile == &Tile::Black { + black_tiles += 1; + } + } + } + if white_tiles == black_tiles { + self.state = State::Tie; + } else if white_tiles > black_tiles { + self.state = State::WhiteWin; + } else { + self.state = State::BlackWin; + } + } } self.current_number = None; } else { @@ -124,6 +159,16 @@ impl WindowLike for Reversi { } } } + if self.state != State::InProgress { + instructions.push(DrawInstructions::Rect([0, 0], [self.dimensions[0], 25], theme_info.background)); + instructions.push(DrawInstructions::Text([4, 4], vec!["times-new-roman".to_string()], if self.state == State::WhiteWin { + "White wins, press any key to restart" + } else if self.state == State::BlackWin { + "Black wins, press any key to restart" + } else { + "Tie, press any key to restart" + }.to_string(), theme_info.text, theme_info.background, None, None)); + } instructions } diff --git a/src/bin/terminal.rs b/src/bin/terminal.rs index 02949d5..69187c2 100644 --- a/src/bin/terminal.rs +++ b/src/bin/terminal.rs @@ -7,7 +7,7 @@ use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use ming_wm::messages::{ WindowMessage, WindowMessageResponse }; use ming_wm::framebuffer::Dimensions; use ming_wm::themes::ThemeInfo; -use ming_wm::utils::concat_paths; +use ming_wm::utils::{ concat_paths, Substring }; use ming_wm::ipc::listen; const MONO_WIDTH: u8 = 10; @@ -54,7 +54,7 @@ impl WindowLike for Terminal { 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(); + self.current_input = self.current_input.remove_last(); } else { return WindowMessageResponse::DoNothing; } diff --git a/src/utils.rs b/src/utils.rs index 29ecdf7..8e62417 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; pub trait Substring { fn substring(&self, start: usize, end: usize) -> &str; fn remove(&self, index: usize, len: usize) -> String; + fn remove_last(&self) -> String; } impl Substring for String { @@ -25,7 +26,11 @@ impl Substring for String { } fn remove(&self, index: usize, len: usize) -> String { - self.substring(0, index).to_string() + self.substring(index + len, self.len()) + self.substring(0, index).to_string() + self.substring(index + len, self.chars().count()) + } + + fn remove_last(&self) -> String { + self.substring(0, self.chars().count() - 1).to_string() } }