file explorer file info, multi-byte char deleting fix
more docs, reversi game win/lose/tie and restart
This commit is contained in:
@@ -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).
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
23
docs/window-likes/audio-player.md
Normal file
23
docs/window-likes/audio-player.md
Normal 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/さよならプリンセス
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,29 +107,38 @@ 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();
|
||||||
//top bar with path name
|
if self.state == State::List {
|
||||||
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));
|
//top bar with path name
|
||||||
//the actual files and directories
|
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));
|
||||||
let mut start_y = HEIGHT;
|
//the actual files and directories
|
||||||
let mut i = self.top_position;
|
let mut start_y = HEIGHT;
|
||||||
for entry in self.current_dir_contents.iter().skip(self.top_position) {
|
let mut i = self.top_position;
|
||||||
if start_y > self.dimensions[1] {
|
for entry in self.current_dir_contents.iter().skip(self.top_position) {
|
||||||
break;
|
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;
|
} else if self.state == State::Info {
|
||||||
if is_selected {
|
let metadata = self.metadata.clone().unwrap();
|
||||||
instructions.push(DrawInstructions::Rect([0, start_y], [self.dimensions[0], HEIGHT], theme_info.top));
|
let mut start_y = HEIGHT;
|
||||||
}
|
let bytes_len = metadata.len();
|
||||||
//unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
|
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));
|
||||||
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;
|
start_y += HEIGHT;
|
||||||
i += 1;
|
//todo: other stuff
|
||||||
}
|
}
|
||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
@@ -165,4 +190,3 @@ impl FileExplorer {
|
|||||||
pub fn main() {
|
pub fn main() {
|
||||||
listen(FileExplorer::new());
|
listen(FileExplorer::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user