Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4876e5606 | |||
|
|
497beb7bb0 | ||
|
|
40f6795163 | ||
|
|
10daa9982b |
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ming-wm"
|
name = "ming-wm"
|
||||||
version = "1.2.0"
|
version = "1.2.3"
|
||||||
repository = "https://github.com/stjet/ming-wm"
|
repository = "https://github.com/stjet/ming-wm"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -11,6 +11,20 @@ default-run = "ming"
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [ "wm", "linux" ]
|
members = [ "wm", "linux" ]
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bmp-rust = "0.5.0"
|
bmp-rust = "0.5.0"
|
||||||
bitcoin_hashes = { version = "0.16.0", default-features = false }
|
bitcoin_hashes = { version = "0.16.0", default-features = false }
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,8 +1,10 @@
|
|||||||
Ming-wm is a keyboard-based, retro-themed window manager for Linux. It 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 my previous projects [ming-de](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 neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/5a598287-27d5-40f2-9b5f-9c1e67cab2f5
|
||||||
|
|
||||||
The [Koxinga web browser](https://github.com/stjet/koxinga) can be separately installed.
|
The [Koxinga web browser](https://github.com/stjet/koxinga) can be separately installed.
|
||||||
|
|
||||||

|

|
||||||
@@ -65,6 +67,20 @@ ming touch rotate
|
|||||||
|
|
||||||
<image alt="mobile example" src="/docs/images/mobile.png" width="50%">
|
<image alt="mobile example" src="/docs/images/mobile.png" width="50%">
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
If key presses do nothing, that is, if on the lockscreen, despite key presses (physical or of the OSK), nothing happens (no asterisk characters show up), try adding the `force-stdout` arg. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
ming force-stdout
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```
|
||||||
|
ming touch force-stdout
|
||||||
|
```
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
See [/docs/philosophy.md](/docs/philosophy.md) for some hopefully interesting ramblings.
|
See [/docs/philosophy.md](/docs/philosophy.md) for some hopefully interesting ramblings.
|
||||||
|
|||||||
BIN
bmps/arhants1440x842.bmp
Normal file
BIN
bmps/arhants1440x842.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 122 KiB |
@@ -14,22 +14,28 @@ It is probably best to read a Vim tutorial for the basics. All supportd keystrok
|
|||||||
- `w[rite]`
|
- `w[rite]`
|
||||||
- `/<query>`
|
- `/<query>`
|
||||||
|
|
||||||
Tab completion is supported for the `<file>` argument.
|
Tab completion is supported for the `<file>` argument. Down arrow will clear the current command, and up arrow will fill in the last ran command.
|
||||||
|
|
||||||
### Supported in Normal Mode
|
### Supported in Normal Mode
|
||||||
|
|
||||||
- `:`
|
- `:`
|
||||||
- `i`
|
- `i`
|
||||||
|
- `o`, `O`
|
||||||
- `A`
|
- `A`
|
||||||
- `r`
|
- `r`
|
||||||
- `dd`
|
- `dd`
|
||||||
- `dw`
|
- `<number>dd`
|
||||||
|
- `dw` (`dw` is not identical to vim's behaviour), `dW`
|
||||||
- `d$`
|
- `d$`
|
||||||
- `G`
|
- `G`
|
||||||
- `gg`
|
- `gg`
|
||||||
- `<number>gg`
|
- `<number>gg`
|
||||||
- `f<char>`
|
- `f<char>`, `F<char>`
|
||||||
- `F<char>`
|
- `<number>f<char>`, `<number>F<char>`
|
||||||
|
- `;` (same as `f<char>` but with the char the cursor is on, so not the same as vim)
|
||||||
|
- `<num>;`
|
||||||
|
- `,` (same as `F<char>` but with the char the cursor is on, so not the same as vim)
|
||||||
|
- `<num>,`
|
||||||
- `x`
|
- `x`
|
||||||
- `h` (or left arrow), `j` (or down arrow), `k` (or up arrow), `l` (or right arrow)
|
- `h` (or left arrow), `j` (or down arrow), `k` (or up arrow), `l` (or right arrow)
|
||||||
- `<num>h`, `<num>j` (or down arrow), `<num>k` (or up arrow), `<num>l`
|
- `<num>h`, `<num>j` (or down arrow), `<num>k` (or up arrow), `<num>l`
|
||||||
|
|||||||
@@ -4,5 +4,18 @@ version = "0.1.0"
|
|||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
useless_conversion = "allow"
|
||||||
|
unnecessary_mut_passed = "allow"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ impl Iterator for RawStdin {
|
|||||||
b'D' => Key::ArrowLeft,
|
b'D' => Key::ArrowLeft,
|
||||||
_ => Key::Other(n),
|
_ => Key::Other(n),
|
||||||
}
|
}
|
||||||
} else if n.is_ok() {
|
} else if let Ok(n) = n {
|
||||||
//Alt+<char> sends Esc+<char>
|
//Alt+<char> sends Esc+<char>
|
||||||
Key::Alt(char::from(n.unwrap()))
|
Key::Alt(char::from(n))
|
||||||
} else {
|
} else {
|
||||||
Key::Esc
|
Key::Esc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ming-wm-lib"
|
name = "ming-wm-lib"
|
||||||
version = "0.2.1"
|
version = "0.2.3"
|
||||||
repository = "https://github.com/stjet/ming-wm"
|
repository = "https://github.com/stjet/ming-wm"
|
||||||
description = "library for building windows for ming-wm in rust"
|
description = "library for building windows for ming-wm in rust"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
len_zero = "allow"
|
||||||
|
comparison_to_empty = "allow"
|
||||||
|
manual_saturating_arithmetic = "allow"
|
||||||
|
result_unit_err = "allow"
|
||||||
|
needless_borrow = "allow"
|
||||||
|
needless_borrows_for_generic_args = "allow"
|
||||||
|
redundant_static_lifetimes = "allow"
|
||||||
|
collapsible_else_if = "allow"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
126
ming-wm-lib/src/fonts.rs
Normal file
126
ming-wm-lib/src/fonts.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::dirs;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FontCharInfo {
|
||||||
|
pub c: char,
|
||||||
|
pub data: Vec<Vec<u8>>,
|
||||||
|
pub top_offset: u8,
|
||||||
|
pub height: usize,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_font_char(dir: &str, c: char) -> Option<FontCharInfo> {
|
||||||
|
let c = if c == '/' { '𐘋' } else if c == '\\' { '𐚆' } else if c == '.' { '𐘅' } else { c };
|
||||||
|
if let Ok(mut file) = File::open(dir.to_string() + "/" + &c.to_string() + ".alpha") {
|
||||||
|
let mut ch: Vec<Vec<u8>> = Vec::new();
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents).unwrap();
|
||||||
|
let lines: Vec<&str> = contents.split("\n").collect();
|
||||||
|
for ln in lines.iter().skip(1) {
|
||||||
|
//.unwrap_or(0) is important because zeroes are just empty
|
||||||
|
ch.push(ln.replace(":", ",,,,").replace(";", ",,,").replace(".", ",,").split(",").map(|n| n.parse().unwrap_or(0)).collect());
|
||||||
|
}
|
||||||
|
return Some(FontCharInfo {
|
||||||
|
c,
|
||||||
|
top_offset: lines[0].parse().unwrap(),
|
||||||
|
height: lines.len() - 1,
|
||||||
|
width: ch[0].len(),
|
||||||
|
data: ch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> FontCharInfo {
|
||||||
|
for font in fonts {
|
||||||
|
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &font))).to_string_lossy().to_string();
|
||||||
|
if let Some(font_char) = get_font_char(&p, c) {
|
||||||
|
return font_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &fonts[0]))).to_string_lossy().to_string();
|
||||||
|
//so a ? char should be in every font. otherwise will just return blank
|
||||||
|
get_font_char(&p, '?').unwrap_or(FontCharInfo {
|
||||||
|
c: '?',
|
||||||
|
data: vec![vec![0]],
|
||||||
|
top_offset: 0,
|
||||||
|
height: 1,
|
||||||
|
width: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MeasureInfo {
|
||||||
|
pub height: usize,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_text(fonts: &[String], text: &str, horiz_spacing: Option<usize>) -> MeasureInfo {
|
||||||
|
let mut height = 0;
|
||||||
|
let mut width = 0;
|
||||||
|
for c in text.chars() {
|
||||||
|
let i = get_font_char_from_fonts(fonts, c);
|
||||||
|
let c_height = i.top_offset as usize + i.height;
|
||||||
|
if c_height > height {
|
||||||
|
height = c_height;
|
||||||
|
}
|
||||||
|
width += i.width + horiz_spacing.unwrap_or(1);
|
||||||
|
}
|
||||||
|
width -= horiz_spacing.unwrap_or(1);
|
||||||
|
MeasureInfo {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure_text_with_cache(fc_getter: &mut CachedFontCharGetter, fonts: &[String], text: &str, horiz_spacing: Option<usize>) -> MeasureInfo {
|
||||||
|
let mut height = 0;
|
||||||
|
let mut width = 0;
|
||||||
|
for c in text.chars() {
|
||||||
|
let i = fc_getter.get(fonts, c);
|
||||||
|
let c_height = i.top_offset as usize + i.height;
|
||||||
|
if c_height > height {
|
||||||
|
height = c_height;
|
||||||
|
}
|
||||||
|
width += i.width + horiz_spacing.unwrap_or(1);
|
||||||
|
}
|
||||||
|
width -= horiz_spacing.unwrap_or(1);
|
||||||
|
MeasureInfo {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CachedFontCharGetter {
|
||||||
|
cache: HashMap<char, FontCharInfo>,
|
||||||
|
cache_size: usize, //# of items cached
|
||||||
|
pub max_cache_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CachedFontCharGetter {
|
||||||
|
pub fn new(max_cache_size: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
max_cache_size,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, fonts: &[String], c: char) -> FontCharInfo {
|
||||||
|
if let Some(cached) = self.cache.get(&c) {
|
||||||
|
cached.clone()
|
||||||
|
} else {
|
||||||
|
let got = get_font_char_from_fonts(fonts, c);
|
||||||
|
if self.cache_size == self.max_cache_size {
|
||||||
|
self.cache_size = 0;
|
||||||
|
self.cache = HashMap::new();
|
||||||
|
}
|
||||||
|
self.cache.insert(c, got.clone());
|
||||||
|
self.cache_size += 1;
|
||||||
|
got
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ pub mod serialize;
|
|||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
pub mod fonts;
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
|||||||
@@ -44,11 +44,7 @@ pub enum WindowMessageResponse {
|
|||||||
|
|
||||||
impl WindowMessageResponse {
|
impl WindowMessageResponse {
|
||||||
pub fn is_key_char_request(&self) -> bool {
|
pub fn is_key_char_request(&self) -> bool {
|
||||||
if let WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(_)) = self {
|
matches!(self, WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(_)))
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl Serializable for ThemeInfo {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut theme_info: ThemeInfo = Default::default();
|
let mut theme_info: ThemeInfo = Default::default();
|
||||||
let arrays = serialized.split(":");
|
let arrays = serialized.split(":");
|
||||||
//won't error or panic if less than 9... rest will just be black by default I guess
|
//won't error or panic if less than 9... rest will just be black by default I guess
|
||||||
@@ -162,7 +162,7 @@ impl Serializable for WindowMessageResponse {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut parts = serialized.split("/");
|
let mut parts = serialized.split("/");
|
||||||
match parts.next().unwrap_or("Invalid") {
|
match parts.next().unwrap_or("Invalid") {
|
||||||
"JustRedraw" => Ok(WindowMessageResponse::JustRedraw),
|
"JustRedraw" => Ok(WindowMessageResponse::JustRedraw),
|
||||||
@@ -444,12 +444,12 @@ impl Serializable for DrawInstructionsVec {
|
|||||||
if self.len() == 0 {
|
if self.len() == 0 {
|
||||||
return "empty".to_string();
|
return "empty".to_string();
|
||||||
}
|
}
|
||||||
let collected: Vec<_> = self.into_iter().map(|ins| ins.serialize()).collect();
|
let collected: Vec<_> = self.iter().map(|ins| ins.serialize()).collect();
|
||||||
collected.join("\x1D")
|
collected.join("\x1D")
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
if serialized == "empty" {
|
if serialized == "empty" {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
@@ -504,7 +504,7 @@ impl Serializable for WindowLikeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
match serialized {
|
match serialized {
|
||||||
"LockScreen" => Ok(WindowLikeType::LockScreen),
|
"LockScreen" => Ok(WindowLikeType::LockScreen),
|
||||||
"Window" => Ok(WindowLikeType::Window),
|
"Window" => Ok(WindowLikeType::Window),
|
||||||
@@ -531,7 +531,7 @@ impl Serializable for Dimensions {
|
|||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
//strip newline at the end
|
//strip newline at the end
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let d = get_two_array(serialized)?;
|
let d = get_two_array(serialized)?;
|
||||||
Ok(d)
|
Ok(d)
|
||||||
}
|
}
|
||||||
@@ -592,7 +592,7 @@ impl Serializable for WindowMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
fn deserialize(serialized: &str) -> Result<Self, ()> {
|
||||||
let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized };
|
let serialized = serialized.strip_suffix("\n").unwrap_or(serialized);
|
||||||
let mut parts = serialized.split("/");
|
let mut parts = serialized.split("/");
|
||||||
match parts.next().unwrap_or("Invalid") {
|
match parts.next().unwrap_or("Invalid") {
|
||||||
"Init" => {
|
"Init" => {
|
||||||
@@ -716,9 +716,9 @@ impl Serializable for WindowMessage {
|
|||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
if let Ok(n) = arg2.unwrap().parse() {
|
if let Ok(n) = arg2.unwrap().parse() {
|
||||||
return Ok(WindowMessage::Info(InfoType::WindowsInWorkspace(w_vec, n)));
|
Ok(WindowMessage::Info(InfoType::WindowsInWorkspace(w_vec, n)))
|
||||||
} else {
|
} else {
|
||||||
return Err(());
|
Err(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Focus" => Ok(WindowMessage::Focus),
|
"Focus" => Ok(WindowMessage::Focus),
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ pub enum Themes {
|
|||||||
//Parchment,
|
//Parchment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Themes {
|
impl std::str::FromStr for Themes {
|
||||||
pub fn from_str(name: &str) -> Option<Self> {
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||||
match name {
|
match name {
|
||||||
"Standard" => Some(Themes::Standard),
|
"Standard" => Ok(Themes::Standard),
|
||||||
"Night" => Some(Themes::Night),
|
"Night" => Ok(Themes::Night),
|
||||||
"Industrial" => Some(Themes::Industrial),
|
"Industrial" => Ok(Themes::Industrial),
|
||||||
"Forest" => Some(Themes::Forest),
|
"Forest" => Ok(Themes::Forest),
|
||||||
"Royal" => Some(Themes::Royal),
|
"Royal" => Ok(Themes::Royal),
|
||||||
_ => None,
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs::read_dir;
|
use std::fs::read_dir;
|
||||||
|
|
||||||
|
use crate::fonts::measure_text;
|
||||||
use crate::framebuffer_types::{ Dimensions, Point };
|
use crate::framebuffer_types::{ Dimensions, Point };
|
||||||
|
|
||||||
pub fn min(one: usize, two: usize) -> usize {
|
pub fn min(one: usize, two: usize) -> usize {
|
||||||
@@ -96,6 +97,23 @@ pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_
|
|||||||
actual_lines
|
actual_lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// truncate to ... if too long (uses `measure_text`)
|
||||||
|
pub fn trunc_words(fonts: &[String], to_measure: String, horiz_spacing: Option<usize>, max_text_width: usize) -> String {
|
||||||
|
if measure_text(fonts, &to_measure, horiz_spacing).width > max_text_width {
|
||||||
|
let mut current = String::new();
|
||||||
|
for c in to_measure.chars() {
|
||||||
|
let to_measure = current.clone() + &c.to_string() + "...";
|
||||||
|
if measure_text(fonts, &to_measure, horiz_spacing).width > max_text_width {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current += &c.to_string();
|
||||||
|
}
|
||||||
|
current + "..."
|
||||||
|
} else {
|
||||||
|
to_measure.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn concat_paths(current_path: &str, add_path: &str) -> Result<PathBuf, ()> {
|
pub fn concat_paths(current_path: &str, add_path: &str) -> Result<PathBuf, ()> {
|
||||||
let mut new_path = PathBuf::from(current_path);
|
let mut new_path = PathBuf::from(current_path);
|
||||||
//if current_path is a file, automatically uses it's parent (a directory)
|
//if current_path is a file, automatically uses it's parent (a directory)
|
||||||
@@ -223,3 +241,4 @@ pub fn get_all_files(dir: PathBuf) -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLik
|
|||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
|
use ming_wm_lib::fonts::measure_text;
|
||||||
use ming_wm_lib::utils::{ concat_paths, random_u32, get_all_files, path_autocomplete, format_seconds, Substring };
|
use ming_wm_lib::utils::{ concat_paths, random_u32, get_all_files, path_autocomplete, format_seconds, Substring };
|
||||||
use ming_wm_lib::dirs::home;
|
use ming_wm_lib::dirs::home;
|
||||||
use ming_wm_lib::ipc::listen;
|
use ming_wm_lib::ipc::listen;
|
||||||
@@ -150,11 +151,15 @@ impl WindowLike for AudioPlayer {
|
|||||||
let queue = &internal_locked.queue;
|
let queue = &internal_locked.queue;
|
||||||
let current = &queue[queue.len() - sink_len];
|
let current = &queue[queue.len() - sink_len];
|
||||||
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
|
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
|
||||||
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - current_name.len() * MONO_WIDTH as usize / 2, 2], vec!["nimbus-romono".to_string(), "shippori-mincho".to_string()], current_name.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
|
let fonts = ["nimbus-roman".to_string(), "shippori-mincho".to_string()];
|
||||||
|
let cn_width = measure_text(&fonts, ¤t_name, None).width;
|
||||||
|
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - cn_width / 2, 2], fonts.to_vec(), current_name.clone(), theme_info.text, theme_info.background, Some(0), None));
|
||||||
if let Some(artist) = ¤t.2 {
|
if let Some(artist) = ¤t.2 {
|
||||||
let artist_string = "by ".to_string() + &artist;
|
let artist_string = "by ".to_string() + &artist;
|
||||||
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - artist_string.len() * MONO_WIDTH as usize / 2, LINE_HEIGHT + 2], vec!["nimbus-romono".to_string()], artist_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
|
let as_width = measure_text(&fonts, &artist_string, None).width;
|
||||||
|
instructions.push(DrawInstructions::Text([self.dimensions[0] / 2 - as_width / 2, LINE_HEIGHT + 2], fonts.to_vec(), artist_string, theme_info.text, theme_info.background, Some(0), None));
|
||||||
}
|
}
|
||||||
|
//in this case no chance of mincho so MONO_WIDTH method of calculating width is ok
|
||||||
let time_string = format!("{}/{}", format_seconds(internal_locked.sink.get_pos().as_secs()), format_seconds(current.1));
|
let time_string = format!("{}/{}", format_seconds(internal_locked.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!["nimbus-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!["nimbus-romono".to_string()], time_string, theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -241,9 +241,11 @@ impl WindowLike for Draw {
|
|||||||
|
|
||||||
impl Draw {
|
impl Draw {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut d: Self = Default::default();
|
//apparently this is legal. thanks clippy
|
||||||
d.current_linewidth = 1;
|
Self {
|
||||||
d
|
current_linewidth: 1,
|
||||||
|
..Default::default() //no comma here allowed though??
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,13 +111,12 @@ impl WindowLike for FileExplorer {
|
|||||||
self.metadata = Some(metadata(&selected_entry.path).unwrap());
|
self.metadata = Some(metadata(&selected_entry.path).unwrap());
|
||||||
}
|
}
|
||||||
WindowMessageResponse::JustRedraw
|
WindowMessageResponse::JustRedraw
|
||||||
|
} else if self.state == State::Info {
|
||||||
|
//pressing any key to exit info mode
|
||||||
|
self.state = State::List;
|
||||||
|
WindowMessageResponse::JustRedraw
|
||||||
} else {
|
} else {
|
||||||
if self.state == State::Info {
|
WindowMessageResponse::DoNothing
|
||||||
self.state = State::List;
|
|
||||||
WindowMessageResponse::JustRedraw
|
|
||||||
} else {
|
|
||||||
WindowMessageResponse::DoNothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => WindowMessageResponse::DoNothing,
|
_ => WindowMessageResponse::DoNothing,
|
||||||
@@ -128,7 +127,7 @@ impl WindowLike for FileExplorer {
|
|||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
if self.state == State::List {
|
if self.state == State::List {
|
||||||
//top bar with path name
|
//top bar with path name
|
||||||
instructions.push(DrawInstructions::Text([5, 0], vec!["nimbus-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!["nimbus-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + self.current_path.to_string_lossy().as_ref(), theme_info.text, theme_info.background, None, None));
|
||||||
//the actual files and directories
|
//the actual files and directories
|
||||||
let mut start_y = HEIGHT;
|
let mut start_y = HEIGHT;
|
||||||
let mut i = self.top_position;
|
let mut i = self.top_position;
|
||||||
@@ -142,10 +141,10 @@ impl WindowLike for FileExplorer {
|
|||||||
}
|
}
|
||||||
//unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
|
//unwrap_or not used because "Arguments passed to unwrap_or are eagerly evaluated", apparently
|
||||||
let name = entry.override_name.clone();
|
let name = entry.override_name.clone();
|
||||||
let name = if name.is_none() {
|
let name = if let Some(name) = name {
|
||||||
entry.path.file_name().unwrap().to_os_string().into_string().unwrap()
|
name
|
||||||
} else {
|
} else {
|
||||||
name.unwrap()
|
entry.path.file_name().unwrap().to_os_string().into_string().unwrap()
|
||||||
};
|
};
|
||||||
instructions.push(DrawInstructions::Text([5, start_y + 4], vec!["nimbus-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));
|
instructions.push(DrawInstructions::Text([5, start_y + 4], vec!["nimbus-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;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const LINE_HEIGHT: usize = 18;
|
|||||||
const PADDING: usize = 2;
|
const PADDING: usize = 2;
|
||||||
const BAND_HEIGHT: usize = 19;
|
const BAND_HEIGHT: usize = 19;
|
||||||
|
|
||||||
|
const WORD_END: [char; 8] = ['.', ',', ':', '[', ']', '{', '}', ' '];
|
||||||
|
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@@ -43,6 +45,12 @@ enum State {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn is_numberable(&self) -> bool {
|
||||||
|
*self == State::Maybeg || *self == State::Find || *self == State::BackFind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -77,6 +85,7 @@ struct Malvim {
|
|||||||
state: State,
|
state: State,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
prev_command: Option<String>,
|
||||||
bottom_message: Option<String>,
|
bottom_message: Option<String>,
|
||||||
maybe_num: Option<usize>,
|
maybe_num: Option<usize>,
|
||||||
files: Vec<FileInfo>,
|
files: Vec<FileInfo>,
|
||||||
@@ -129,8 +138,8 @@ impl WindowLike for Malvim {
|
|||||||
if key_press.is_enter() {
|
if key_press.is_enter() {
|
||||||
let mut line: Vec<char> = line.chars().collect();
|
let mut line: Vec<char> = line.chars().collect();
|
||||||
let (left, right) = line.split_at_mut(current_file.cursor_pos);
|
let (left, right) = line.split_at_mut(current_file.cursor_pos);
|
||||||
let left = left.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
let left = left.iter_mut().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
||||||
let right = right.into_iter().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
let right = right.iter_mut().map(|c| c.to_string()).collect::<Vec<String>>().join("");
|
||||||
current_file.content[current_file.line_pos] = left.to_string();
|
current_file.content[current_file.line_pos] = left.to_string();
|
||||||
let spaces = Malvim::calc_spaces(self.autoindent, &left);
|
let spaces = Malvim::calc_spaces(self.autoindent, &left);
|
||||||
current_file.content.insert(current_file.line_pos + 1, " ".repeat(spaces) + &right);
|
current_file.content.insert(current_file.line_pos + 1, " ".repeat(spaces) + &right);
|
||||||
@@ -178,7 +187,7 @@ impl WindowLike for Malvim {
|
|||||||
}
|
}
|
||||||
let new_length = current_file.content[current_file.line_pos].chars().count();
|
let new_length = current_file.content[current_file.line_pos].chars().count();
|
||||||
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
|
current_file.cursor_pos = Malvim::calc_new_cursor_pos(current_file.cursor_pos, new_length);
|
||||||
} else if key_press.key == 'w' || key_press.key == '$' {
|
} else if key_press.key == 'w' || key_press.key == 'W' || key_press.key == '$' {
|
||||||
let line = ¤t_file.content[current_file.line_pos];
|
let line = ¤t_file.content[current_file.line_pos];
|
||||||
let line_len = line.chars().count();
|
let line_len = line.chars().count();
|
||||||
if line_len > 0 && current_file.cursor_pos < line_len {
|
if line_len > 0 && current_file.cursor_pos < line_len {
|
||||||
@@ -186,11 +195,19 @@ impl WindowLike for Malvim {
|
|||||||
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
|
let mut line_chars = line.chars().skip(current_file.cursor_pos).peekable();
|
||||||
//deref is Copy
|
//deref is Copy
|
||||||
let current_char = *line_chars.peek().unwrap();
|
let current_char = *line_chars.peek().unwrap();
|
||||||
let offset = if key_press.key == 'w' {
|
let offset = if key_press.key == 'W' || key_press.key == 'w' {
|
||||||
line_chars.position(|c| if current_char == ' ' {
|
line_chars.position(|c| if key_press.key == 'w' {
|
||||||
c != ' '
|
if WORD_END.contains(¤t_char) {
|
||||||
|
c != current_char
|
||||||
|
} else {
|
||||||
|
WORD_END.contains(&c)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c == ' '
|
if current_char == ' ' {
|
||||||
|
c != ' '
|
||||||
|
} else {
|
||||||
|
c == ' '
|
||||||
|
}
|
||||||
}).unwrap_or(line_len - current_file.cursor_pos)
|
}).unwrap_or(line_len - current_file.cursor_pos)
|
||||||
} else {
|
} else {
|
||||||
line_chars.count()
|
line_chars.count()
|
||||||
@@ -212,33 +229,41 @@ impl WindowLike for Malvim {
|
|||||||
}
|
}
|
||||||
changed = false;
|
changed = false;
|
||||||
self.state = State::None;
|
self.state = State::None;
|
||||||
} else if self.state == State::Find || self.state == State::BackFind {
|
} else if self.state == State::Find || self.state == State::BackFind || key_press.key == ';' || key_press.key == ',' {
|
||||||
let old_pos = current_file.cursor_pos;
|
let mut old_pos = current_file.cursor_pos;
|
||||||
let find_pos = if self.state == State::Find {
|
let find_char = if self.state == State::Find || self.state == State::BackFind {
|
||||||
if old_pos < current_file.content[current_file.line_pos].chars().count() {
|
key_press.key
|
||||||
let found_index = current_file.content[current_file.line_pos].chars().skip(old_pos + 1).position(|c| c == key_press.key);
|
|
||||||
if let Some(found_index) = found_index {
|
|
||||||
old_pos + found_index + 1
|
|
||||||
} else {
|
|
||||||
old_pos
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
old_pos
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//how does this work again? no idea
|
current_file.content[current_file.line_pos].chars().nth(old_pos).unwrap()
|
||||||
if old_pos != 0 {
|
};
|
||||||
let found_index = current_file.content[current_file.line_pos].chars().rev().skip(current_length - old_pos).position(|c| c == key_press.key);
|
for _ in 0..self.maybe_num.unwrap_or(1) {
|
||||||
if let Some(found_index) = found_index {
|
let find_pos = if self.state == State::Find || key_press.key == ';' {
|
||||||
old_pos - found_index - 1
|
if old_pos < current_file.content[current_file.line_pos].chars().count() {
|
||||||
|
let found_index = current_file.content[current_file.line_pos].chars().skip(old_pos + 1).position(|c| c == find_char);
|
||||||
|
if let Some(found_index) = found_index {
|
||||||
|
old_pos + found_index + 1
|
||||||
|
} else {
|
||||||
|
old_pos
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
old_pos
|
old_pos
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
old_pos //0
|
//how does this work again? no idea
|
||||||
}
|
if old_pos != 0 {
|
||||||
};
|
let found_index = current_file.content[current_file.line_pos].chars().rev().skip(current_length - old_pos).position(|c| c == find_char);
|
||||||
current_file.cursor_pos = find_pos;
|
if let Some(found_index) = found_index {
|
||||||
|
old_pos - found_index - 1
|
||||||
|
} else {
|
||||||
|
old_pos
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
old_pos //0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
current_file.cursor_pos = find_pos;
|
||||||
|
old_pos = current_file.cursor_pos;
|
||||||
|
}
|
||||||
changed = false;
|
changed = false;
|
||||||
self.state = State::None;
|
self.state = State::None;
|
||||||
} else if key_press.key == 'x' {
|
} else if key_press.key == 'x' {
|
||||||
@@ -394,7 +419,7 @@ impl WindowLike for Malvim {
|
|||||||
changed = false;
|
changed = false;
|
||||||
}
|
}
|
||||||
//reset maybe_num if not num
|
//reset maybe_num if not num
|
||||||
if !numbered && self.state != State::Maybeg && self.state != State::MaybeDelete {
|
if !numbered && !self.state.is_numberable() {
|
||||||
self.maybe_num = None;
|
self.maybe_num = None;
|
||||||
}
|
}
|
||||||
} else if self.mode == Mode::Command {
|
} else if self.mode == Mode::Command {
|
||||||
@@ -402,7 +427,8 @@ impl WindowLike for Malvim {
|
|||||||
let command = self.command.clone().unwrap_or("".to_string());
|
let command = self.command.clone().unwrap_or("".to_string());
|
||||||
if key_press.is_enter() {
|
if key_press.is_enter() {
|
||||||
new = self.process_command();
|
new = self.process_command();
|
||||||
self.command = None;
|
self.prev_command = self.command.take();
|
||||||
|
//line above does same as `self.command = None`
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
} else if key_press.key == '\t' { //tab
|
} else if key_press.key == '\t' { //tab
|
||||||
let mut parts = command.split(" ").skip(1);
|
let mut parts = command.split(" ").skip(1);
|
||||||
@@ -425,6 +451,12 @@ impl WindowLike for Malvim {
|
|||||||
if command.len() > 0 {
|
if command.len() > 0 {
|
||||||
self.command = Some(command.remove_last());
|
self.command = Some(command.remove_last());
|
||||||
}
|
}
|
||||||
|
} else if key_press.is_arrow() {
|
||||||
|
if key_press.is_up_arrow() {
|
||||||
|
self.command = self.prev_command.clone();
|
||||||
|
} else if key_press.is_down_arrow() {
|
||||||
|
self.command = Some(String::new());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command = Some(command.to_string() + &key_press.key.to_string());
|
self.command = Some(command.to_string() + &key_press.key.to_string());
|
||||||
}
|
}
|
||||||
@@ -463,7 +495,7 @@ impl WindowLike for Malvim {
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
//modify current line
|
//modify current line
|
||||||
let line = ¤t_file.content[current_file.line_pos];
|
let line = ¤t_file.content[current_file.line_pos];
|
||||||
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + &cs + line.substring(current_file.cursor_pos, line.chars().count());
|
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + cs + line.substring(current_file.cursor_pos, line.chars().count());
|
||||||
current_file.cursor_pos += copy_string.len();
|
current_file.cursor_pos += copy_string.len();
|
||||||
} else {
|
} else {
|
||||||
//insert a new line
|
//insert a new line
|
||||||
@@ -718,7 +750,7 @@ impl Malvim {
|
|||||||
}
|
}
|
||||||
} else if self.files.len() == 0 {
|
} else if self.files.len() == 0 {
|
||||||
self.bottom_message = Some("No files are open, so can only do :e(dit)".to_string());
|
self.bottom_message = Some("No files are open, so can only do :e(dit)".to_string());
|
||||||
} else if first.starts_with("/") {
|
} else if let Some(s_first) = first.strip_prefix("/") {
|
||||||
let current_file = &mut self.files[self.current_file_index];
|
let current_file = &mut self.files[self.current_file_index];
|
||||||
if current_file.content.len() > 0 {
|
if current_file.content.len() > 0 {
|
||||||
let p1 = if arg == "" {
|
let p1 = if arg == "" {
|
||||||
@@ -732,7 +764,7 @@ impl Malvim {
|
|||||||
} else {
|
} else {
|
||||||
" ".to_string() + &rest
|
" ".to_string() + &rest
|
||||||
};
|
};
|
||||||
let query = first[1..].to_string() + &p1 + &rest;
|
let query = s_first.to_string() + &p1 + &rest;
|
||||||
let mut lines = current_file.content.iter().skip(current_file.line_pos);
|
let mut lines = current_file.content.iter().skip(current_file.line_pos);
|
||||||
for i in 0..(current_file.content.len() - current_file.line_pos) {
|
for i in 0..(current_file.content.len() - current_file.line_pos) {
|
||||||
let line = if i == 0 {
|
let line = if i == 0 {
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ impl Minesweeper {
|
|||||||
self.random_seed as usize % 16
|
self.random_seed as usize % 16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize) -> (), if_mine: bool) {
|
pub fn on_adjacent_tiles(&self, x: usize, y: usize, mut action: impl FnMut(usize, usize), if_mine: bool) {
|
||||||
if y > 0 {
|
if y > 0 {
|
||||||
//above
|
//above
|
||||||
if self.tiles[y - 1][x].mine == if_mine {
|
if self.tiles[y - 1][x].mine == if_mine {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
|
||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
|
||||||
@@ -101,13 +102,11 @@ impl WindowLike for Reversi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if white_tiles == black_tiles {
|
self.state = match white_tiles.cmp(&black_tiles) {
|
||||||
self.state = State::Tie;
|
Ordering::Equal => State::Tie,
|
||||||
} else if white_tiles > black_tiles {
|
Ordering::Greater => State::WhiteWin,
|
||||||
self.state = State::WhiteWin;
|
Ordering::Less => State::BlackWin,
|
||||||
} else {
|
};
|
||||||
self.state = State::BlackWin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.current_number = None;
|
self.current_number = None;
|
||||||
|
|||||||
@@ -149,27 +149,23 @@ impl WindowLike for Terminal {
|
|||||||
Mode::Running => {
|
Mode::Running => {
|
||||||
//update
|
//update
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
loop {
|
while let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
|
||||||
if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
|
if char::from(ci) == '\n' {
|
||||||
if char::from(ci) == '\n' {
|
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
||||||
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
|
self.output += &append_line;
|
||||||
self.output += &append_line;
|
self.output += "\n";
|
||||||
self.output += "\n";
|
self.lines.push(append_line);
|
||||||
self.lines.push(append_line);
|
self.process_current_line = Vec::new();
|
||||||
self.process_current_line = Vec::new();
|
} else if char::from(ci) == '\r' {
|
||||||
} else if char::from(ci) == '\r' {
|
//for now, ignore
|
||||||
//for now, ignore
|
//
|
||||||
//
|
} else if char::from(ci) == '\t' {
|
||||||
} else if char::from(ci) == '\t' {
|
//for now, interpret as space
|
||||||
//for now, interpret as space
|
self.process_current_line.push(b' ');
|
||||||
self.process_current_line.push(b' ');
|
|
||||||
} else {
|
|
||||||
self.process_current_line.push(ci);
|
|
||||||
}
|
|
||||||
changed = true;
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
self.process_current_line.push(ci);
|
||||||
}
|
}
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
let running_process = self.running_process.as_mut().unwrap();
|
let running_process = self.running_process.as_mut().unwrap();
|
||||||
if let Some(_status) = running_process.try_wait().unwrap() {
|
if let Some(_status) = running_process.try_wait().unwrap() {
|
||||||
@@ -377,13 +373,9 @@ impl Terminal {
|
|||||||
let mut stdin = self.running_process.as_mut().unwrap().stdin.take().unwrap();
|
let mut stdin = self.running_process.as_mut().unwrap().stdin.take().unwrap();
|
||||||
let (tx2, rx2) = channel();
|
let (tx2, rx2) = channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
while let Ok(write_line) = rx2.recv() {
|
||||||
if let Ok(write_line) = rx2.recv() {
|
let write_line: String = write_line + "\n";
|
||||||
let write_line: String = write_line + "\n";
|
stdin.write_all(write_line.as_bytes()).unwrap();
|
||||||
stdin.write(write_line.as_bytes()).unwrap();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.pty_outerr_rx = Some(rx1);
|
self.pty_outerr_rx = Some(rx1);
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let touch = args.contains(&"touch".to_string());
|
let touch = args.contains(&"touch".to_string());
|
||||||
|
let force_stdout = args.contains(&"force-stdout".to_string()); //write to stdout to force framebuffer redraw
|
||||||
|
|
||||||
//read touchscreen presses (hopefully)
|
//read touchscreen presses (hopefully)
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
@@ -152,10 +153,17 @@ fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
|||||||
|
|
||||||
for message in rx {
|
for message in rx {
|
||||||
match message {
|
match message {
|
||||||
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
|
ThreadMessage::KeyChar(kc) => {
|
||||||
|
wm.handle_message(WindowManagerMessage::KeyChar(kc.clone()));
|
||||||
|
if force_stdout {
|
||||||
|
println!(" "); //without any stdout, on some user's devices, for some reason the framebuffer doesn't get redrawn to the screen
|
||||||
|
}
|
||||||
|
},
|
||||||
ThreadMessage::Touch(x, y) => {
|
ThreadMessage::Touch(x, y) => {
|
||||||
wm.handle_message(WindowManagerMessage::Touch(x, y));
|
wm.handle_message(WindowManagerMessage::Touch(x, y));
|
||||||
println!(" "); //without any stdout, on my phone, for some reason the framebuffer doesn't get redrawn to the screen
|
if force_stdout {
|
||||||
|
println!(" "); //without any stdout, on my phone, for some reason the framebuffer doesn't get redrawn to the screen
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ThreadMessage::Clear => {
|
ThreadMessage::Clear => {
|
||||||
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
|
write!(stdout.stdout, "{}", CLEAR_ALL).unwrap();
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ repository = "https://github.com/stjet/ming-wm"
|
|||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ming-wm-lib = { path = "../ming-wm-lib" }
|
ming-wm-lib = { path = "../ming-wm-lib" }
|
||||||
linux = { path = "../linux" }
|
linux = { path = "../linux" }
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl WindowLike for DesktopBackground {
|
|||||||
}
|
}
|
||||||
} else if line.len() > 1 {
|
} else if line.len() > 1 {
|
||||||
//first character of line is either r or any other character, but is not part of the path
|
//first character of line is either r or any other character, but is not part of the path
|
||||||
return vec![DrawInstructions::Bmp([0, 0], line[1..].to_string(), line.chars().next().unwrap() == 'r')];
|
return vec![DrawInstructions::Bmp([0, 0], line[1..].to_string(), line.starts_with('r'))];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLik
|
|||||||
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
|
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
|
||||||
use ming_wm_lib::framebuffer_types::Dimensions;
|
use ming_wm_lib::framebuffer_types::Dimensions;
|
||||||
use ming_wm_lib::themes::ThemeInfo;
|
use ming_wm_lib::themes::ThemeInfo;
|
||||||
|
use ming_wm_lib::utils::trunc_words;
|
||||||
use ming_wm_lib::components::Component;
|
use ming_wm_lib::components::Component;
|
||||||
use ming_wm_lib::components::toggle_button::ToggleButton;
|
use ming_wm_lib::components::toggle_button::ToggleButton;
|
||||||
|
|
||||||
@@ -78,7 +79,8 @@ impl WindowLike for Taskbar {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let info = &self.windows_in_workspace[wi];
|
let info = &self.windows_in_workspace[wi];
|
||||||
let name = &info.1;
|
let max_text_width = META_WIDTH - PADDING * 2;
|
||||||
|
let name = trunc_words(&["nimbus-roman".to_string()], info.1.clone(), None, max_text_width);
|
||||||
let mut b = ToggleButton::new(name.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], name.to_string(), TaskbarMessage::Nothing, TaskbarMessage::Nothing);
|
let mut b = ToggleButton::new(name.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], name.to_string(), TaskbarMessage::Nothing, TaskbarMessage::Nothing);
|
||||||
b.inverted = info.0 == self.focused_id;
|
b.inverted = info.0 == self.focused_id;
|
||||||
instructions.extend(b.draw(theme_info));
|
instructions.extend(b.draw(theme_info));
|
||||||
@@ -123,5 +125,3 @@ impl Taskbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ use std::vec::Vec;
|
|||||||
use bmp_rust::bmp::BMP;
|
use bmp_rust::bmp::BMP;
|
||||||
|
|
||||||
use ming_wm_lib::framebuffer_types::*;
|
use ming_wm_lib::framebuffer_types::*;
|
||||||
use crate::fs::get_font_char_from_fonts;
|
use ming_wm_lib::fonts::{ CachedFontCharGetter, FontCharInfo };
|
||||||
|
|
||||||
type FontChar = (char, Vec<Vec<u8>>, u8);
|
|
||||||
|
|
||||||
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
|
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
|
||||||
/*let factor: f32 = alpha as f32 / 255.0;
|
/*let factor: f32 = alpha as f32 / 255.0;
|
||||||
@@ -47,6 +45,7 @@ pub struct FramebufferInfo {
|
|||||||
//currently doesn't check if writing onto next line accidentally
|
//currently doesn't check if writing onto next line accidentally
|
||||||
pub struct FramebufferWriter {
|
pub struct FramebufferWriter {
|
||||||
info: FramebufferInfo,
|
info: FramebufferInfo,
|
||||||
|
fc_getter: CachedFontCharGetter,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
saved_buffer: Option<Vec<u8>>,
|
saved_buffer: Option<Vec<u8>>,
|
||||||
rotate_buffer: Option<Vec<u8>>,
|
rotate_buffer: Option<Vec<u8>>,
|
||||||
@@ -57,6 +56,7 @@ impl FramebufferWriter {
|
|||||||
pub fn new(grayscale: bool) -> Self {
|
pub fn new(grayscale: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
|
fc_getter: CachedFontCharGetter::new(128), //an arbitrary high-ish number for max cache size
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
saved_buffer: None,
|
saved_buffer: None,
|
||||||
rotate_buffer: None,
|
rotate_buffer: None,
|
||||||
@@ -128,16 +128,13 @@ impl FramebufferWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_char(&mut self, top_left: Point, char_info: &FontChar, color: RGBColor, bg_color: RGBColor) {
|
pub fn draw_char(&mut self, top_left: Point, char_info: &FontCharInfo, color: RGBColor, bg_color: RGBColor) {
|
||||||
let mut start_pos;
|
let mut start_pos;
|
||||||
for row in 0..char_info.1.len() {
|
for row in 0..char_info.height {
|
||||||
//char_info.2 is vertical offset
|
start_pos = ((top_left[1] + row + char_info.top_offset as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
|
||||||
start_pos = ((top_left[1] + row + char_info.2 as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
|
for col in &char_info.data[row] {
|
||||||
for col in &char_info.1[row] {
|
if col > &0 && start_pos + 3 < self.info.byte_len {
|
||||||
if col > &0 {
|
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
|
||||||
if start_pos + 3 < self.info.byte_len {
|
|
||||||
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
start_pos += self.info.bytes_per_pixel;
|
start_pos += self.info.bytes_per_pixel;
|
||||||
}
|
}
|
||||||
@@ -226,8 +223,8 @@ impl FramebufferWriter {
|
|||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
top_left[0] += mono_width.unwrap_or(5) as usize;
|
top_left[0] += mono_width.unwrap_or(5) as usize;
|
||||||
} else {
|
} else {
|
||||||
let char_info = get_font_char_from_fonts(&fonts, c);
|
let char_info = self.fc_getter.get(&fonts, c);
|
||||||
let char_width = char_info.1[0].len();
|
let char_width = char_info.width;
|
||||||
let add_after: usize;
|
let add_after: usize;
|
||||||
if let Some(mono_width) = mono_width {
|
if let Some(mono_width) = mono_width {
|
||||||
let mono_width = mono_width as usize;
|
let mono_width = mono_width as usize;
|
||||||
|
|||||||
31
wm/src/fs.rs
31
wm/src/fs.rs
@@ -1,38 +1,9 @@
|
|||||||
use std::fs::{ read_dir, File };
|
use std::fs::read_dir;
|
||||||
use std::io::Read;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use ming_wm_lib::dirs;
|
use ming_wm_lib::dirs;
|
||||||
use ming_wm_lib::utils::get_rest_of_split;
|
use ming_wm_lib::utils::get_rest_of_split;
|
||||||
|
|
||||||
fn get_font_char(dir: &str, c: char) -> Option<(char, Vec<Vec<u8>>, u8)> {
|
|
||||||
let c = if c == '/' { '𐘋' } else if c == '\\' { '𐚆' } else if c == '.' { '𐘅' } else { c };
|
|
||||||
if let Ok(mut file) = File::open(dir.to_string() + "/" + &c.to_string() + ".alpha") {
|
|
||||||
let mut ch: Vec<Vec<u8>> = Vec::new();
|
|
||||||
let mut contents = String::new();
|
|
||||||
file.read_to_string(&mut contents).unwrap();
|
|
||||||
let lines: Vec<&str> = contents.split("\n").collect();
|
|
||||||
for ln in 1..lines.len() {
|
|
||||||
//.unwrap_or(0) is important because zeroes are just empty
|
|
||||||
ch.push(lines[ln].replace(":", ",,,,").replace(";", ",,,").replace(".", ",,").split(",").map(|n| n.parse().unwrap_or(0)).collect());
|
|
||||||
}
|
|
||||||
return Some((c, ch, lines[0].parse().unwrap()));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> (char, Vec<Vec<u8>>, u8) {
|
|
||||||
for font in fonts {
|
|
||||||
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &font))).to_string_lossy().to_string();
|
|
||||||
if let Some(font_char) = get_font_char(&p, c) {
|
|
||||||
return font_char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &fonts[0]))).to_string_lossy().to_string();
|
|
||||||
//so a ? char should be in every font. otherwise will just return blank
|
|
||||||
get_font_char(&p, '?').unwrap_or(('?', vec![vec![0]], 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
//Category, Vec<Display name, file name>
|
//Category, Vec<Display name, file name>
|
||||||
pub type ExeWindowInfos = HashMap<String, Vec<(String, String)>>;
|
pub type ExeWindowInfos = HashMap<String, Vec<(String, String)>>;
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ impl ProxyWindowLike {
|
|||||||
if let Some(buffer) = buffer.stdout.as_mut() {
|
if let Some(buffer) = buffer.stdout.as_mut() {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let mut reader = BufReader::new(buffer);
|
let mut reader = BufReader::new(buffer);
|
||||||
if let Ok(_) = reader.read_line(&mut output) {
|
if reader.read_line(&mut output).is_ok() {
|
||||||
output
|
output
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ use std::boxed::Box;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use linux::fb::Framebuffer;
|
use linux::fb::Framebuffer;
|
||||||
use ming_wm_lib::framebuffer_types::{ Point, Dimensions };
|
use ming_wm_lib::framebuffer_types::{ Point, Dimensions };
|
||||||
use ming_wm_lib::themes::{ Themes, get_theme_info };
|
use ming_wm_lib::themes::{ Themes, get_theme_info };
|
||||||
use ming_wm_lib::utils::{ min, point_inside };
|
use ming_wm_lib::utils::{ min, point_inside, trunc_words };
|
||||||
use ming_wm_lib::messages::*;
|
use ming_wm_lib::messages::*;
|
||||||
use ming_wm_lib::dirs::config_dir;
|
use ming_wm_lib::dirs::config_dir;
|
||||||
use ming_wm_lib::window_manager_types::*;
|
use ming_wm_lib::window_manager_types::*;
|
||||||
@@ -168,8 +169,9 @@ impl WindowManager {
|
|||||||
//if off_only is true, also handle request
|
//if off_only is true, also handle request
|
||||||
//written confusingly but it works I promise
|
//written confusingly but it works I promise
|
||||||
fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse {
|
fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse {
|
||||||
let start_menu_exists = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::StartMenu).is_some();
|
let start_menu_exists = self.window_infos.iter().any(|w| w.window_like.subtype() == WindowLikeType::StartMenu);
|
||||||
if (start_menu_exists && off_only) || !off_only {
|
//if (start_menu_exists && off_only) || !off_only {
|
||||||
|
if start_menu_exists || !off_only {
|
||||||
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
|
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
|
||||||
self.focused_id = self.window_infos[taskbar_index].id;
|
self.focused_id = self.window_infos[taskbar_index].id;
|
||||||
if off_only {
|
if off_only {
|
||||||
@@ -710,13 +712,15 @@ impl WindowManager {
|
|||||||
//draw window background
|
//draw window background
|
||||||
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
|
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
|
||||||
//draw window top decorations and what not
|
//draw window top decorations and what not
|
||||||
|
let title = trunc_words(&["nimbus-roman".to_string()], window_info.window_like.title(), None, window_dimensions[0]);
|
||||||
instructions.extend(vec![
|
instructions.extend(vec![
|
||||||
//left top border
|
//left top border
|
||||||
DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top),
|
DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top),
|
||||||
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
|
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
|
||||||
//top
|
//top
|
||||||
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
|
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
|
||||||
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], window_info.window_like.title().to_string(), theme_info.top_text, theme_info.top, None, None),
|
//window title
|
||||||
|
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], title, theme_info.top_text, theme_info.top, None, None),
|
||||||
//top bottom border
|
//top bottom border
|
||||||
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
|
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
|
||||||
//right bottom border
|
//right bottom border
|
||||||
|
|||||||
Reference in New Issue
Block a user