file explorer file info, multi-byte char deleting fix

more docs, reversi game win/lose/tie and restart
This commit is contained in:
stjet
2025-01-25 23:04:20 +00:00
parent 03f1d649e0
commit 8d0a317819
8 changed files with 139 additions and 39 deletions

View File

@@ -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). 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 1](/docs/images/ws1.png)
![example 2](/docs/images/ws2.png) ![example 2](/docs/images/ws3.png)
## Running ## Running

View File

@@ -49,13 +49,13 @@ The event loop goes like this:
1. Keyboard event received, sent to the window manager 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 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`: 3. (Only if sent to a window-like) The window-like receives and processes it. It returns a `WindowMessageResponse`:
>>> ```rust > ```rust
pub enum WindowMessageResponse { > pub enum WindowMessageResponse {
Request(WindowManagerRequest), > Request(WindowManagerRequest),
JustRerender, > JustRerender,
DoNothing, > 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 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. 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.

View File

@@ -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 <dir>`: Set base directory (`<dir>` is path)
- `p <dir / playlist file>`: Play audio files in `<dir>` or play the songs listed in the `<playlist file>`. Unless paths are absolute, they will be relative to the directory specified by the `b <dir>` command
## Playlists
Example playlist file:
```
hanyuu-maigo/オノマトペ
inabakumori/*
iyowa/*
kai/さよならプリンセス
```

View File

@@ -12,7 +12,7 @@ use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm::messages::{ WindowMessage, WindowMessageResponse }; use ming_wm::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm::framebuffer::Dimensions; use ming_wm::framebuffer::Dimensions;
use ming_wm::themes::ThemeInfo; 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::fs::get_all_files;
use ming_wm::ipc::listen; use ming_wm::ipc::listen;
@@ -47,7 +47,7 @@ impl WindowLike for AudioPlayer {
self.command = String::new(); self.command = String::new();
} else if key_press.key == '𐘁' { //backspace } else if key_press.key == '𐘁' { //backspace
if self.command.len() > 0 { if self.command.len() > 0 {
self.command = self.command[..self.command.len() - 1].to_string(); self.command = self.command.remove_last();
} }
} else { } else {
self.command += &key_press.key.to_string(); 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)); 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))); 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 instructions

View File

@@ -1,6 +1,6 @@
use std::vec::Vec; use std::vec::Vec;
use std::vec; use std::vec;
use std::fs::read_dir; use std::fs::{ read_dir, metadata, Metadata };
use std::path::PathBuf; use std::path::PathBuf;
use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
@@ -18,6 +18,13 @@ struct DirectoryChild {
is_file: bool, is_file: bool,
} }
#[derive(Default, PartialEq)]
enum State {
#[default]
List,
Info,
}
#[derive(Default)] #[derive(Default)]
pub struct FileExplorer { pub struct FileExplorer {
dimensions: Dimensions, dimensions: Dimensions,
@@ -26,6 +33,8 @@ pub struct FileExplorer {
//for scrolling and selecting dirs //for scrolling and selecting dirs
position: usize, position: usize,
top_position: usize, top_position: usize,
state: State,
metadata: Option<Metadata>,
} }
impl WindowLike for FileExplorer { impl WindowLike for FileExplorer {
@@ -42,6 +51,7 @@ impl WindowLike for FileExplorer {
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
}, },
WindowMessage::KeyPress(key_press) => { WindowMessage::KeyPress(key_press) => {
self.state = State::List;
if key_press.key == '𐘂' { //the enter key if key_press.key == '𐘂' { //the enter key
if self.current_dir_contents.len() > 0 { if self.current_dir_contents.len() > 0 {
let selected_entry = &self.current_dir_contents[self.position]; let selected_entry = &self.current_dir_contents[self.position];
@@ -73,14 +83,20 @@ impl WindowLike for FileExplorer {
//calculate position //calculate position
let max_height = self.dimensions[1] - HEIGHT; let max_height = self.dimensions[1] - HEIGHT;
if self.position > self.top_position { 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] { 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 { } else {
self.top_position = self.position; self.top_position = self.position;
}; };
WindowMessageResponse::JustRedraw 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 { } else {
WindowMessageResponse::DoNothing WindowMessageResponse::DoNothing
} }
@@ -91,6 +107,7 @@ impl WindowLike for FileExplorer {
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> { fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = Vec::new(); let mut instructions = Vec::new();
if self.state == State::List {
//top bar with path name //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)); 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 //the actual files and directories
@@ -115,6 +132,14 @@ impl WindowLike for FileExplorer {
start_y += HEIGHT; start_y += HEIGHT;
i += 1; i += 1;
} }
} 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;
//todo: other stuff
}
instructions instructions
} }
@@ -165,4 +190,3 @@ impl FileExplorer {
pub fn main() { pub fn main() {
listen(FileExplorer::new()); listen(FileExplorer::new());
} }

View File

@@ -43,6 +43,15 @@ impl Tile {
} }
} }
#[derive(Default, PartialEq)]
enum State {
#[default]
InProgress,
WhiteWin,
BlackWin,
Tie,
}
#[derive(Default)] #[derive(Default)]
struct Reversi { struct Reversi {
dimensions: Dimensions, dimensions: Dimensions,
@@ -50,6 +59,7 @@ struct Reversi {
current_number: Option<u8>, //the first number of the tile that user wants to place piece on current_number: Option<u8>, //the first number of the tile that user wants to place piece on
valid_moves: Vec<ValidMove>, valid_moves: Vec<ValidMove>,
white_turn: bool, //if false, black turn white_turn: bool, //if false, black turn
state: State,
} }
impl WindowLike for Reversi { impl WindowLike for Reversi {
@@ -62,7 +72,11 @@ impl WindowLike for Reversi {
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
}, },
WindowMessage::KeyPress(key_press) => { WindowMessage::KeyPress(key_press) => {
if let Ok(n) = key_press.key.to_string().parse::<u8>() { 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::<u8>() {
if let Some(current_number) = self.current_number { if let Some(current_number) = self.current_number {
let y = current_number as usize; let y = current_number as usize;
let x = n as usize; let x = n as usize;
@@ -74,6 +88,27 @@ impl WindowLike for Reversi {
} }
self.white_turn = !self.white_turn; self.white_turn = !self.white_turn;
self.valid_moves = self.get_valid_moves(); 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; self.current_number = None;
} else { } 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 instructions
} }

View File

@@ -7,7 +7,7 @@ use ming_wm::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm::messages::{ WindowMessage, WindowMessageResponse }; use ming_wm::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm::framebuffer::Dimensions; use ming_wm::framebuffer::Dimensions;
use ming_wm::themes::ThemeInfo; use ming_wm::themes::ThemeInfo;
use ming_wm::utils::concat_paths; use ming_wm::utils::{ concat_paths, Substring };
use ming_wm::ipc::listen; use ming_wm::ipc::listen;
const MONO_WIDTH: u8 = 10; const MONO_WIDTH: u8 = 10;
@@ -54,7 +54,7 @@ impl WindowLike for Terminal {
if self.state == State::Input { if self.state == State::Input {
if key_press.key == '𐘁' { //backspace if key_press.key == '𐘁' { //backspace
if self.current_input.len() > 0 { 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 { } else {
return WindowMessageResponse::DoNothing; return WindowMessageResponse::DoNothing;
} }

View File

@@ -3,6 +3,7 @@ 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;
fn remove(&self, index: usize, len: usize) -> String; fn remove(&self, index: usize, len: usize) -> String;
fn remove_last(&self) -> String;
} }
impl Substring for String { impl Substring for String {
@@ -25,7 +26,11 @@ impl Substring for String {
} }
fn remove(&self, index: usize, len: usize) -> 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()
} }
} }