diff --git a/Cargo.toml b/Cargo.toml index 25a7d18..83d3655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ming-wm" -version = "1.2.0" +version = "1.2.1" repository = "https://github.com/stjet/ming-wm" license = "GPL-3.0-or-later" edition = "2021" diff --git a/bmps/arhants1440x842.bmp b/bmps/arhants1440x842.bmp new file mode 100644 index 0000000..ededfff Binary files /dev/null and b/bmps/arhants1440x842.bmp differ diff --git a/docs/window-likes/malvim.md b/docs/window-likes/malvim.md index 659bf8c..a80cc65 100644 --- a/docs/window-likes/malvim.md +++ b/docs/window-likes/malvim.md @@ -14,22 +14,28 @@ It is probably best to read a Vim tutorial for the basics. All supportd keystrok - `w[rite]` - `/` -Tab completion is supported for the `` argument. +Tab completion is supported for the `` argument. Down arrow will clear the current command, and up arrow will fill in the last ran command. ### Supported in Normal Mode - `:` - `i` +- `o`, `O` - `A` - `r` - `dd` -- `dw` +- `dd` +- `dw` (`dw` is not identical to vim's behaviour), `dW` - `d$` - `G` - `gg` - `gg` -- `f` -- `F` +- `f`, `F` +- `f`, `F` +- `;` (same as `f` but with the char the cursor is on, so not the same as vim) +- `;` +- `,` (same as `F` but with the char the cursor is on, so not the same as vim) +- `,` - `x` - `h` (or left arrow), `j` (or down arrow), `k` (or up arrow), `l` (or right arrow) - `h`, `j` (or down arrow), `k` (or up arrow), `l` diff --git a/ming-wm-lib/Cargo.toml b/ming-wm-lib/Cargo.toml index 7a9d44d..2228d4b 100644 --- a/ming-wm-lib/Cargo.toml +++ b/ming-wm-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ming-wm-lib" -version = "0.2.1" +version = "0.2.2" repository = "https://github.com/stjet/ming-wm" description = "library for building windows for ming-wm in rust" readme = "README.md" diff --git a/ming-wm-lib/src/fonts.rs b/ming-wm-lib/src/fonts.rs new file mode 100644 index 0000000..81cb64f --- /dev/null +++ b/ming-wm-lib/src/fonts.rs @@ -0,0 +1,107 @@ +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>, + pub top_offset: u8, + pub height: usize, + pub width: usize, +} + +fn get_font_char(dir: &str, c: char) -> Option { + 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::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(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, +} + +/// Doesn't take into account `horiz_spacing`, which defaults to 1 per character +pub fn measure_text(fonts: &[String], text: String) -> 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; + } + MeasureInfo { + height, + width, + } +} + +#[derive(Default)] +pub struct CachedFontCharGetter { + cache: HashMap, + cache_size: usize, //# of items cached + pub max_cache_size: usize, +} + +impl CachedFontCharGetter { + pub fn new(max_cache_size: usize) -> Self { + let mut s: Self = Default::default(); + s.max_cache_size = max_cache_size; + s + } + + 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 + } + } +} diff --git a/ming-wm-lib/src/lib.rs b/ming-wm-lib/src/lib.rs index 96cb963..3ca09db 100644 --- a/ming-wm-lib/src/lib.rs +++ b/ming-wm-lib/src/lib.rs @@ -5,6 +5,7 @@ pub mod serialize; pub mod messages; pub mod ipc; pub mod components; +pub mod fonts; pub mod dirs; pub mod utils; pub mod logging; diff --git a/ming-wm-lib/src/utils.rs b/ming-wm-lib/src/utils.rs index 717c40c..5a44775 100644 --- a/ming-wm-lib/src/utils.rs +++ b/ming-wm-lib/src/utils.rs @@ -223,3 +223,4 @@ pub fn get_all_files(dir: PathBuf) -> Vec { } files } + diff --git a/src/bin/audio_player.rs b/src/bin/audio_player.rs index ad38bc9..557e04d 100644 --- a/src/bin/audio_player.rs +++ b/src/bin/audio_player.rs @@ -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::framebuffer_types::Dimensions; 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::dirs::home; use ming_wm_lib::ipc::listen; @@ -150,11 +151,15 @@ impl WindowLike for AudioPlayer { let queue = &internal_locked.queue; let current = &queue[queue.len() - sink_len]; 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, current_name.clone()).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 { 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.clone()).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)); 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 { diff --git a/src/bin/malvim.rs b/src/bin/malvim.rs index 93dec4f..53f5b09 100644 --- a/src/bin/malvim.rs +++ b/src/bin/malvim.rs @@ -19,6 +19,8 @@ const LINE_HEIGHT: usize = 18; const PADDING: usize = 2; const BAND_HEIGHT: usize = 19; +const WORD_END: [char; 8] = ['.', ',', ':', '[', ']', '{', '}', ' ']; + struct FileInfo { pub name: 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)] enum Mode { #[default] @@ -77,6 +85,7 @@ struct Malvim { state: State, mode: Mode, command: Option, + prev_command: Option, bottom_message: Option, maybe_num: Option, files: Vec, @@ -178,7 +187,7 @@ impl WindowLike for Malvim { } 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); - } 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_len = line.chars().count(); 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(); //deref is Copy let current_char = *line_chars.peek().unwrap(); - let offset = if key_press.key == 'w' { - line_chars.position(|c| if current_char == ' ' { - c != ' ' + let offset = if key_press.key == 'W' || key_press.key == 'w' { + line_chars.position(|c| if key_press.key == 'w' { + if WORD_END.contains(¤t_char) { + c != current_char + } else { + WORD_END.contains(&c) + } } else { - c == ' ' + if current_char == ' ' { + c != ' ' + } else { + c == ' ' + } }).unwrap_or(line_len - current_file.cursor_pos) } else { line_chars.count() @@ -212,33 +229,41 @@ impl WindowLike for Malvim { } changed = false; self.state = State::None; - } else if self.state == State::Find || self.state == State::BackFind { - let old_pos = current_file.cursor_pos; - let find_pos = if self.state == State::Find { - 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 == key_press.key); - if let Some(found_index) = found_index { - old_pos + found_index + 1 - } else { - old_pos - } - } else { - old_pos - } + } else if self.state == State::Find || self.state == State::BackFind || key_press.key == ';' || key_press.key == ',' { + let mut old_pos = current_file.cursor_pos; + let find_char = if self.state == State::Find || self.state == State::BackFind { + key_press.key } else { - //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 == key_press.key); - if let Some(found_index) = found_index { - old_pos - found_index - 1 + current_file.content[current_file.line_pos].chars().nth(old_pos).unwrap() + }; + for _ in 0..self.maybe_num.unwrap_or(1) { + let find_pos = if self.state == State::Find || key_press.key == ';' { + 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 { old_pos } } else { - old_pos //0 - } - }; - current_file.cursor_pos = find_pos; + //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); + 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; self.state = State::None; } else if key_press.key == 'x' { @@ -394,7 +419,7 @@ impl WindowLike for Malvim { changed = false; } //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; } } else if self.mode == Mode::Command { @@ -402,7 +427,8 @@ impl WindowLike for Malvim { let command = self.command.clone().unwrap_or("".to_string()); if key_press.is_enter() { 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; } else if key_press.key == '\t' { //tab let mut parts = command.split(" ").skip(1); @@ -425,6 +451,12 @@ impl WindowLike for Malvim { if command.len() > 0 { 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 { self.command = Some(command.to_string() + &key_press.key.to_string()); } diff --git a/wm/src/essential/taskbar.rs b/wm/src/essential/taskbar.rs index f806590..b2f0fd3 100644 --- a/wm/src/essential/taskbar.rs +++ b/wm/src/essential/taskbar.rs @@ -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::framebuffer_types::Dimensions; use ming_wm_lib::themes::ThemeInfo; +use ming_wm_lib::fonts::measure_text; use ming_wm_lib::components::Component; use ming_wm_lib::components::toggle_button::ToggleButton; @@ -78,7 +79,25 @@ impl WindowLike for Taskbar { break; } let info = &self.windows_in_workspace[wi]; - let name = &info.1; + let max_text_width = META_WIDTH - PADDING * 2; + //horiz_spacing is by default 1 per char, which measure_text doesn't take into account + let to_measure = info.1.clone(); + let to_measure_len = to_measure.chars().count(); + let name = if measure_text(&["nimbus-roman".to_string()], to_measure).width + to_measure_len > max_text_width { + let mut current = String::new(); + for c in info.1.chars() { + //horiz_spacing is 1 by default + let to_measure = current.clone() + &c.to_string() + "..."; + let to_measure_len = to_measure.chars().count(); + if measure_text(&["nimbus-roman".to_string()], to_measure).width + to_measure_len > max_text_width { + break; + } + current += &c.to_string(); + } + current + "..." + } else { + info.1.clone() + }; 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; instructions.extend(b.draw(theme_info)); @@ -123,5 +142,3 @@ impl Taskbar { } } } - - diff --git a/wm/src/framebuffer.rs b/wm/src/framebuffer.rs index 9601447..26ad89c 100644 --- a/wm/src/framebuffer.rs +++ b/wm/src/framebuffer.rs @@ -4,9 +4,7 @@ use std::vec::Vec; use bmp_rust::bmp::BMP; use ming_wm_lib::framebuffer_types::*; -use crate::fs::get_font_char_from_fonts; - -type FontChar = (char, Vec>, u8); +use ming_wm_lib::fonts::{ CachedFontCharGetter, FontCharInfo }; fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor { /*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 pub struct FramebufferWriter { info: FramebufferInfo, + fc_getter: CachedFontCharGetter, buffer: Vec, saved_buffer: Option>, rotate_buffer: Option>, @@ -57,6 +56,7 @@ impl FramebufferWriter { pub fn new(grayscale: bool) -> Self { Self { info: Default::default(), + fc_getter: CachedFontCharGetter::new(128), //an arbitrary high-ish number for max cache size buffer: Vec::new(), saved_buffer: None, rotate_buffer: None, @@ -128,12 +128,11 @@ 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; - for row in 0..char_info.1.len() { - //char_info.2 is vertical offset - 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.1[row] { + for row in 0..char_info.height { + start_pos = ((top_left[1] + row + char_info.top_offset as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for col in &char_info.data[row] { if col > &0 { if start_pos + 3 < self.info.byte_len { self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col)); @@ -226,8 +225,8 @@ impl FramebufferWriter { if c == ' ' { top_left[0] += mono_width.unwrap_or(5) as usize; } else { - let char_info = get_font_char_from_fonts(&fonts, c); - let char_width = char_info.1[0].len(); + let char_info = self.fc_getter.get(&fonts, c); + let char_width = char_info.width; let add_after: usize; if let Some(mono_width) = mono_width { let mono_width = mono_width as usize; diff --git a/wm/src/fs.rs b/wm/src/fs.rs index 9129505..cf682ec 100644 --- a/wm/src/fs.rs +++ b/wm/src/fs.rs @@ -1,38 +1,9 @@ -use std::fs::{ read_dir, File }; -use std::io::Read; +use std::fs::read_dir; use std::collections::HashMap; use ming_wm_lib::dirs; use ming_wm_lib::utils::get_rest_of_split; -fn get_font_char(dir: &str, c: char) -> Option<(char, Vec>, 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::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>, 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 pub type ExeWindowInfos = HashMap>;