improve window render, malvim draft, fixes

This commit is contained in:
stjet
2024-10-17 23:49:27 +00:00
parent edf293185f
commit 4311b424c8
126 changed files with 487 additions and 77 deletions

View File

@@ -38,12 +38,12 @@ impl<T: Clone> Component<T> for HighlightButton<T> {
vec![
//highlight background
DrawInstructions::Rect(self.top_left, self.size, theme_info.top),
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text_top, theme_info.top, None),
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.top_text, theme_info.top, None, None),
]
} else {
vec![
DrawInstructions::Rect(self.top_left, self.size, theme_info.background),
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None),
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None, None),
]
}
}

View File

@@ -54,7 +54,7 @@ impl<T: Clone> Component<T> for ToggleButton<T> {
//the background if self.draw_bg
//DrawInstructions::Rect(),
//the text (for now, hardcoded top left)
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None),
DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], "times-new-roman", self.text.to_string(), theme_info.text, theme_info.background, None, None),
]
}

View File

@@ -164,27 +164,26 @@ impl FramebufferWriter {
//todo, config space
for c in text.chars() {
if c == ' ' {
top_left[0] += 5;
top_left[0] += mono_width.unwrap_or(5) as usize;
} else {
let char_info = get_font_char(&("./bmps/".to_string() + font_name), c);
if let Some(char_info) = char_info {
let char_width = char_info.1[0].len();
let add_after: usize;
if let Some(mono_width) = mono_width {
let mono_width = mono_width as usize;
let remainder = if mono_width < char_width {
0
} else {
mono_width - char_width
};
//so a ? char must be in every font
let char_info = get_font_char(&("./bmps/".to_string() + font_name), c).unwrap_or(get_font_char(&("./bmps/".to_string() + font_name), '?').unwrap());
let char_width = char_info.1[0].len();
let add_after: usize;
if let Some(mono_width) = mono_width {
let mono_width = mono_width as usize;
if mono_width < char_width {
add_after = mono_width;
} else {
let remainder = mono_width - char_width;
top_left[0] += remainder / 2;
add_after = remainder - remainder / 2 + char_width;
} else {
add_after = char_width + horiz_spacing;
}
self.draw_char(top_left, &char_info, color, bg_color);
top_left[0] += add_after;
};
} else {
add_after = char_width + horiz_spacing;
}
self.draw_char(top_left, &char_info, color, bg_color);
top_left[0] += add_after;
}
}
}

View File

@@ -4,7 +4,6 @@ use bmp_rust::bmp::BMP;
pub fn get_font_char(dir: &str, c: char) -> Option<(char, Vec<Vec<u8>>, u8)> {
let c = if c == '/' { '𐘋' } else { c };
let mut font: Vec<(char, Vec<Vec<u8>>, u8)> = Vec::new();
for entry in read_dir(dir).unwrap() {
let path = entry.unwrap().path();
let path_chars: Vec<char> = path.file_name().unwrap().to_str().unwrap().to_string().chars().collect();

View File

@@ -13,6 +13,7 @@ pub fn key_to_char(key: Key) -> Option<KeyChar> {
Key::Char(c) => Some(KeyChar::Press(c)),
Key::Alt(c) => Some(KeyChar::Alt(c)),
Key::Backspace => Some(KeyChar::Press('𐘁')),
Key::Esc => Some(KeyChar::Press('𐘃')),
_ => None,
}
}

View File

@@ -18,6 +18,8 @@ mod messages;
mod fs;
mod utils;
fn main() {
let mut fb = Framebuffer::new("/dev/fb0").unwrap();
let bytes_per_pixel = (fb.var_screen_info.bits_per_pixel as usize) / 8;

View File

@@ -69,6 +69,7 @@ pub enum ShortcutType {
MoveWindowToEdge(Direction),
CenterWindow,
FullscreenWindow,
HalfWidthWindow, //half width, full height
//
}

View File

@@ -13,9 +13,10 @@ pub struct ThemeInfo {
pub border_left_top: RGBColor,
pub border_right_bottom: RGBColor,
pub text: RGBColor,
pub text_top: RGBColor,
pub top_text: RGBColor,
pub alt_background: RGBColor,
pub alt_text: RGBColor,
pub alt_secondary: RGBColor,
//
}
@@ -26,9 +27,10 @@ const THEME_INFOS: [(Themes, ThemeInfo); 1] = [
border_left_top: [255, 255, 255],
border_right_bottom: [0, 0, 0],
text: [0, 0, 0],
text_top: [255, 255, 255],
top_text: [255, 255, 255],
alt_background: [0, 0, 0],
alt_text: [255, 255, 255],
alt_secondary: [128, 128, 128],
//
}),
];

28
src/utils.rs Normal file
View File

@@ -0,0 +1,28 @@
//the tuple is first, line #, actual line
pub fn calc_actual_lines<'a>(lines: impl Iterator<Item = &'a String>, max_chars_per_line: usize) -> Vec<(bool, usize, String)> {
let mut actual_lines = Vec::new();
let mut line_num = 0;
for real_line in lines {
let mut line = real_line.to_string();
let mut first = true;
loop {
if line.chars().count() <= max_chars_per_line {
actual_lines.push((first, line_num, line));
break;
} else {
let mut line_chars = line.chars();
let mut push_string = String::new();
for _i in 0..max_chars_per_line {
push_string += &line_chars.next().unwrap().to_string();
}
actual_lines.push((first, line_num, push_string));
line = line_chars.collect();
}
first = false;
}
line_num += 1;
}
actual_lines
}

View File

@@ -50,11 +50,11 @@ impl WindowLike for LockScreen {
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
vec![
DrawInstructions::Rect([0, 0], self.dimensions, [0, 0, 0]),
DrawInstructions::Text([4, 4], "times-new-roman", "The bulldozer outside the kitchen window was quite a big one.".to_string(), [255, 255, 255], [0, 0, 0], None),
DrawInstructions::Text([4, 4 + 16], "times-new-roman", "\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(), [255, 255, 255], [0, 0, 0], None),
DrawInstructions::Text([4, 4 + 16 * 2], "times-new-roman", "He stared at it.".to_string(), [255, 255, 255], [0, 0, 0], None),
DrawInstructions::Text([4, 4 + 16 * 3], "times-new-roman", "Password: ".to_string(), [255, 255, 255], [0, 0, 0], None),
DrawInstructions::Text([77, 4 + 16 * 3], "times-new-roman", "*".repeat(self.input_password.len()), [255, 255, 255], [0, 0, 0], None),
DrawInstructions::Text([4, 4], "times-new-roman", "The bulldozer outside the kitchen window was quite a big one.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16], "times-new-roman", "\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 2], "times-new-roman", "He stared at it.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 3], "times-new-roman", "Password: ".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([77, 4 + 16 * 3], "times-new-roman", "*".repeat(self.input_password.len()), [255, 255, 255], [0, 0, 0], None, None),
]
}

354
src/window_likes/malvim.rs Normal file
View File

@@ -0,0 +1,354 @@
use std::vec::Vec;
use std::vec;
use std::fmt;
use std::path::PathBuf;
use std::fs::{ read_to_string, write };
use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::themes::ThemeInfo;
use crate::framebuffer::Dimensions;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use crate::utils::calc_actual_lines;
const MONO_WIDTH: u8 = 10;
const LINE_HEIGHT: usize = 18;
const PADDING: usize = 2;
const BAND_HEIGHT: usize = 18;
struct FileInfo {
pub name: String,
pub path: String,
pub changed: bool,
pub top_line_pos: usize,
pub line_pos: usize,
pub cursor_pos: usize,
pub content: Vec<String>,
//
}
#[derive(Default, PartialEq)]
enum State {
#[default]
None,
//
}
#[derive(Default, PartialEq)]
enum Mode {
#[default]
Normal,
Insert,
Command,
}
impl fmt::Display for Mode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let write_str = match self {
Mode::Normal => "NORMAL",
Mode::Insert => "INSERT",
Mode::Command => "COMMAND",
};
fmt.write_str(write_str)?;
Ok(())
}
}
#[derive(Default)]
struct Current {
pub actual_lines: Vec<(bool, usize, String)>, //first, line #, actual line
pub line_num_width: usize, //file line digits * MONO_WIDTH
pub max_lines: usize, //max actual lines on screen
pub max_chars_per_line: usize,
}
#[derive(Default)]
pub struct Malvim {
dimensions: Dimensions,
state: State,
mode: Mode,
command: Option<String>,
bottom_message: Option<String>,
maybe_num: Option<usize>,
files: Vec<FileInfo>,
current_file_index: usize,
current: Current,
}
impl WindowLike for Malvim {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
WindowMessage::KeyPress(key_press) => {
if key_press.key == '𐘃' { //escape key
self.mode = Mode::Normal;
self.state = State::None;
} else if key_press.key == ':' && self.mode == Mode::Normal && self.state == State::None {
self.mode = Mode::Command;
self.command = Some(String::new());
} else if key_press.key == 'i' && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 {
self.mode = Mode::Insert;
} else if self.mode == Mode::Insert {
let current_file = &self.files[self.current_file_index];
if key_press.key == '𐘂' { //the enter key
//
} else if key_press.key == '𐘁' { //backspace
//
} else {
//
}
} else if self.mode == Mode::Normal && self.files.len() > 0 {
let current_file = &mut self.files[self.current_file_index];
if key_press.key == 'h' {
current_file.cursor_pos = current_file.cursor_pos.checked_sub(1).unwrap_or(0);
} else if key_press.key == 'j' {
//
} else if key_press.key == 'k' {
//
} else if key_press.key == 'l' {
current_file.cursor_pos += 1;
if current_file.cursor_pos == current_file.content[current_file.line_pos].len() {
current_file.cursor_pos = current_file.content[current_file.line_pos].len() - 1;
}
} else if key_press.key == '0' {
current_file.cursor_pos = 0;
} else if key_press.key == '$' {
current_file.cursor_pos = current_file.content[current_file.line_pos].len().checked_sub(1).unwrap_or(0);
}
//
} else if self.mode == Mode::Command {
self.bottom_message = None;
let command = self.command.clone().unwrap_or("".to_string());
if key_press.key == '𐘂' { //the enter key
self.process_command();
self.command = None;
self.mode = Mode::Normal;
} else if key_press.key == '𐘁' { //backspace
if command.len() > 0 {
self.command = Some(command[..command.len() - 1].to_string());
}
} else {
self.command = Some(command.to_string() + &key_press.key.to_string());
}
} else {
return WindowMessageResponse::DoNothing;
}
self.calc_current(); //too over zealous but whatever
WindowMessageResponse::JustRerender
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRerender
},
_ => WindowMessageResponse::DoNothing,
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![
//the top white bar
DrawInstructions::Rect([0, 0], [self.dimensions[0], BAND_HEIGHT], theme_info.alt_text),
//black background
DrawInstructions::Rect([0, BAND_HEIGHT], [self.dimensions[0], self.dimensions[1] - BAND_HEIGHT * 3], theme_info.alt_background),
//slight above bottom blue bar
DrawInstructions::Rect([0, self.dimensions[1] - BAND_HEIGHT * 2], [self.dimensions[0], BAND_HEIGHT], theme_info.top),
//black background
DrawInstructions::Rect([0, self.dimensions[1] - BAND_HEIGHT], [self.dimensions[0], BAND_HEIGHT], theme_info.alt_background),
];
//write file tabs
let mut used_width = 0;
for file_index in 0..self.files.len() {
let file_info = &self.files[file_index];
let future_used_width = used_width + 4 + file_info.name.len() * MONO_WIDTH as usize;
//just cut off when too many file tabs open to fit
if future_used_width > self.dimensions[0] {
break;
}
let background = if file_index == self.current_file_index {
theme_info.alt_background
} else {
theme_info.alt_secondary
};
instructions.extend(vec![
DrawInstructions::Rect([used_width, 2], [file_info.name.len() * MONO_WIDTH as usize + 4, BAND_HEIGHT - 2], background),
DrawInstructions::Text([used_width + 2, 2], "times-new-romono", file_info.name.clone(), theme_info.alt_text, background, Some(0), Some(MONO_WIDTH)),
]);
used_width = future_used_width;
}
//write the actual current file
let mut sub_line_num = 0; //a line in a file can be split into multiple lines for display
if self.files.len() > 0 {
let current_file = &self.files[self.current_file_index];
let current = &self.current;
for line_num in current_file.top_line_pos..(current_file.top_line_pos + current.max_lines) {
if line_num == current.actual_lines.len() {
break;
}
let line = &current.actual_lines[line_num];
let rel_line_num = line_num - current_file.top_line_pos;
//write line num text (if start of line)
let y0 = BAND_HEIGHT + rel_line_num * LINE_HEIGHT + PADDING;
if line.0 {
instructions.push(DrawInstructions::Text([PADDING, y0], "times-new-romono", line.1.to_string(), theme_info.alt_secondary, theme_info.alt_background, Some(0), Some(MONO_WIDTH)));
sub_line_num = 0;
}
let x1 = current.line_num_width + PADDING * 2;
//write actual line
//line.2
instructions.push(DrawInstructions::Text([x1, y0], "times-new-romono", line.2.clone(), theme_info.alt_text, theme_info.alt_background, Some(0), Some(MONO_WIDTH)));
sub_line_num += 1;
let max = sub_line_num * current.max_chars_per_line;
let min = max - current.max_chars_per_line;
if line.1 == current_file.line_pos && current_file.cursor_pos >= min && current_file.cursor_pos < max {
let top_left = [x1 + (current_file.cursor_pos - min) * MONO_WIDTH as usize, y0];
//the cursor is on this line, draw it
instructions.push(DrawInstructions::Rect(top_left, [MONO_WIDTH as usize, LINE_HEIGHT], theme_info.top));
//draw the char over it
instructions.push(DrawInstructions::Text(top_left, "times-new-romono", line.2.chars().nth(current_file.cursor_pos - min).unwrap().to_string(), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
}
}
}
//bottom blue band stuff
//write mode
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - BAND_HEIGHT * 2 + 1], "times-new-romono", self.mode.to_string(), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
let file_status;
if self.files.len() > 0 {
file_status = self.files[self.current_file_index].name.clone();
} else {
file_status = "No file open".to_string();
}
instructions.push(DrawInstructions::Text([self.dimensions[0] - file_status.len() * (MONO_WIDTH as usize), self.dimensions[1] - BAND_HEIGHT * 2 + 1], "times-new-romono", file_status, theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
//write command or bottom message
if self.mode == Mode::Command {
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - BAND_HEIGHT], "times-new-romono", ":".to_string() + &self.command.clone().unwrap_or("".to_string()), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
} else if self.mode == Mode::Normal && self.bottom_message.is_some() {
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - BAND_HEIGHT], "times-new-romono", self.bottom_message.clone().unwrap(), theme_info.top_text, theme_info.top, Some(0), Some(MONO_WIDTH)));
}
instructions
}
fn title(&self) -> &'static str {
"Malvim"
}
fn subtype(&self) -> WindowLikeType {
WindowLikeType::Window
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[410, 410]
}
fn resizable(&self) -> bool {
true
}
}
impl Malvim {
pub fn new() -> Self {
Default::default()
}
fn calc_current(&mut self) {
if self.files.len() == 0 {
return;
}
let current_file = &self.files[self.current_file_index];
let line_num_width = current_file.content.len().to_string().len() * MONO_WIDTH as usize;
let max_chars_per_line = (self.dimensions[0] - line_num_width - PADDING * 2) / MONO_WIDTH as usize;
let actual_lines = calc_actual_lines(current_file.content.iter(), max_chars_per_line);
//now, see if the line_pos is still visible from the top_line_pos,
//if not, move top_line_pos down until it is
let max_lines = (self.dimensions[1] - BAND_HEIGHT * 3 - PADDING) / LINE_HEIGHT;
if current_file.top_line_pos + max_lines < current_file.line_pos {
self.files[self.current_file_index].top_line_pos = current_file.line_pos.checked_sub(max_lines).unwrap_or(0);
}
self.current = Current {
actual_lines,
line_num_width,
max_lines,
max_chars_per_line,
};
}
fn process_command(&mut self) {
let mut parts = self.command.as_ref().unwrap().split(" ");
let first = parts.next().unwrap();
let arg = parts.next().unwrap_or("");
if first == "e" || first == "edit" || ((first == "t" || first == "tabe") && self.files.len() > 0) {
//find the file and open it
let mut failed = false;
let mut new_path = if self.files.len() > 0 {
PathBuf::from(self.files[self.current_file_index].path.clone()).parent().unwrap().to_path_buf()
} else {
PathBuf::from("/")
};
for part in arg.split("/") {
if part == ".." {
if let Some(parent) = new_path.parent() {
new_path = parent.to_path_buf();
} else {
failed = true;
}
} else {
new_path.push(part);
}
}
if !failed && new_path.is_file() {
let name = new_path.file_name().unwrap().to_string_lossy().into_owned();
let path = new_path.to_string_lossy().into_owned();
if let Ok(content) = read_to_string(new_path) {
let file_info = FileInfo {
name,
path,
changed: false,
top_line_pos: 0,
line_pos: 0,
cursor_pos: 0,
content: content.split("\n").map(|s| s.to_string()).collect(),
};
if first == "e" || first == "edit" {
if self.files.len() > 0 {
self.files[self.current_file_index] = file_info;
} else {
self.files.push(file_info);
}
} else {
//t(abe)
self.current_file_index += 1;
if self.current_file_index == self.files.len() - 1 {
self.files.push(file_info);
} else {
self.files.insert(self.current_file_index, file_info);
}
}
} else {
self.bottom_message = Some("Failed to open that file".to_string());
}
} else {
self.bottom_message = Some("That is not a file or does not exist".to_string());
}
} else if self.files.len() == 0 {
self.bottom_message = Some("No files are open, so can only do :e(dit)".to_string());
} else if first == "w" || first == "write" {
let current_file = &self.files[self.current_file_index];
write(&current_file.path, &current_file.content.join("\n"));
self.files[self.current_file_index].changed = false;
} else if first == "q" || first == "quit" {
self.files.remove(self.current_file_index);
self.current_file_index = self.current_file_index.checked_sub(1).unwrap_or(0);
} else if first == "p" || first == "tabp" {
self.current_file_index = self.current_file_index.checked_sub(1).unwrap_or(self.files.len() - 1);
} else if first == "n" || first == "tabn" {
self.current_file_index += 1;
if self.current_file_index == self.files.len() {
self.current_file_index = 0;
}
} else {
self.bottom_message = Some("Not a command".to_string());
}
}
}

View File

@@ -3,7 +3,7 @@ use std::vec;
use std::collections::VecDeque;
use core::convert::TryFrom;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT };
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::framebuffer::Dimensions;
use crate::themes::ThemeInfo;
@@ -156,20 +156,20 @@ impl WindowLike for Minesweeper {
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
if self.state == MinesweeperState::Seed {
vec![
DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "Type in random characters to initalise the seed".to_string(), theme_info.text, theme_info.background, None),
DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4 + 16], "times-new-roman", self.random_chars.clone(), theme_info.text, theme_info.background, None),
DrawInstructions::Text([4, 4], "times-new-roman", "Type in random characters to initalise the seed".to_string(), theme_info.text, theme_info.background, None, None),
DrawInstructions::Text([4, 4 + 16], "times-new-roman", self.random_chars.clone(), theme_info.text, theme_info.background, None, None),
]
} else {
let mut instructions = vec![
//top border
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT], [self.dimensions[0] - 7, 5], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT], [4, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 1], [3, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 2], [2, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 3], [1, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 4], [1, 1], [128, 128, 128]),
DrawInstructions::Rect([1, 0], [self.dimensions[0] - 7, 5], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, 0], [4, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, 1], [3, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, 2], [2, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, 3], [1, 1], [128, 128, 128]),
DrawInstructions::Rect([self.dimensions[0] - 6, 4], [1, 1], [128, 128, 128]),
//left border
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT], [5, self.dimensions[1] - WINDOW_TOP_HEIGHT - 5], [128, 128, 128]),
DrawInstructions::Rect([1, 0], [5, self.dimensions[1] - 5], [128, 128, 128]),
DrawInstructions::Rect([1, self.dimensions[1] - 5], [1, 4], [128, 128, 128]),
DrawInstructions::Rect([2, self.dimensions[1] - 5], [1, 3], [128, 128, 128]),
DrawInstructions::Rect([3, self.dimensions[1] - 5], [1, 2], [128, 128, 128]),
@@ -181,12 +181,12 @@ impl WindowLike for Minesweeper {
DrawInstructions::Rect([3, self.dimensions[1] - 3], [1, 2], [255, 255, 255]),
DrawInstructions::Rect([2, self.dimensions[1] - 2], [1, 1], [255, 255, 255]),
//right border
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 5], [5, self.dimensions[1] - WINDOW_TOP_HEIGHT], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 2, WINDOW_TOP_HEIGHT], [1, 5], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 3, WINDOW_TOP_HEIGHT + 1], [1, 4], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 4, WINDOW_TOP_HEIGHT + 2], [1, 3], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 5, WINDOW_TOP_HEIGHT + 3], [1, 2], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 6, WINDOW_TOP_HEIGHT + 4], [1, 1], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 6, 5], [5, self.dimensions[1]], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 2, 0], [1, 5], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 3, 1], [1, 4], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 4, 2], [1, 3], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 5, 3], [1, 2], [255, 255, 255]),
DrawInstructions::Rect([self.dimensions[0] - 6, 4], [1, 1], [255, 255, 255]),
];
let tile_size = (self.dimensions[0] - 10) / 16;
for y in 0..16 {
@@ -194,7 +194,7 @@ impl WindowLike for Minesweeper {
let tile = &self.tiles[y][x];
if tile.revealed {
if tile.mine {
instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 2, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2], "times-new-roman", "x".to_string(), [255, 0, 0], theme_info.background, None));
instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 2, y * tile_size + tile_size / 2], "times-new-roman", "x".to_string(), [255, 0, 0], theme_info.background, None, None));
} else {
let color = match tile.touching {
1 => [0, 0, 255],
@@ -207,10 +207,10 @@ impl WindowLike for Minesweeper {
//8
_ => [128, 128, 128],
};
instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 5, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2 + 2], "times-new-roman", tile.touching.to_string(), color, theme_info.background, None));
instructions.push(DrawInstructions::Text([x * tile_size + tile_size / 2 + 5, y * tile_size + tile_size / 2 + 2], "times-new-roman", tile.touching.to_string(), color, theme_info.background, None, None));
}
} else {
let top_left = [x * tile_size + 6, WINDOW_TOP_HEIGHT + y * tile_size + 5];
let top_left = [x * tile_size + 6, y * tile_size + 5];
//do not do the corners in respect of our poor poor heap (vector size too big would be bad)
instructions.extend(vec![
//top border
@@ -225,15 +225,15 @@ impl WindowLike for Minesweeper {
//right bottom
DrawInstructions::Rect([top_left[0] + tile_size - 4, top_left[1] + 3], [3, tile_size - 4], [128, 128, 128]),
//
DrawInstructions::Text([x * tile_size + tile_size / 2 - 2, WINDOW_TOP_HEIGHT + y * tile_size + tile_size / 2], "times-new-roman", u8_to_hex((y * 16 + x) as u8), theme_info.text, theme_info.background, None),
DrawInstructions::Text([x * tile_size + tile_size / 2 - 2, y * tile_size + tile_size / 2], "times-new-roman", u8_to_hex((y * 16 + x) as u8), theme_info.text, theme_info.background, None, None),
]);
}
}
}
if self.state == MinesweeperState::Lost {
instructions.extend(vec![DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "You LOST!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None)]);
instructions.extend(vec![DrawInstructions::Text([4, 4], "times-new-roman", "You LOST!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
} else if self.state == MinesweeperState::Won {
instructions.extend(vec![DrawInstructions::Text([4, WINDOW_TOP_HEIGHT + 4], "times-new-roman", "You WON!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None)]);
instructions.extend(vec![DrawInstructions::Text([4, 4], "times-new-roman", "You WON!!! Press a key to play again.".to_string(), theme_info.text, theme_info.background, None, None)]);
}
instructions
}
@@ -249,7 +249,7 @@ impl WindowLike for Minesweeper {
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[410, 410 + WINDOW_TOP_HEIGHT]
[410, 410]
}
}

View File

@@ -6,4 +6,5 @@ pub mod workspace_indicator;
pub mod minesweeper;
pub mod terminal;
pub mod malvim;

View File

@@ -141,6 +141,8 @@ impl StartMenu {
let mut to_add: Vec<&str> = Vec::new();
if name == "Games" {
to_add.push("Minesweeper");
} else if name == "Editing" {
to_add.push("Malvim");
} else if name == "Files" {
to_add.push("Terminal");
}

View File

@@ -5,12 +5,12 @@ use std::str::from_utf8;
use std::path::PathBuf;
use std::io;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT };
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType };
use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::framebuffer::Dimensions;
use crate::themes::ThemeInfo;
const MONO_WIDTH: u8 = 8;
const MONO_WIDTH: u8 = 10;
const LINE_HEIGHT: usize = 15;
const PADDING: usize = 4;
@@ -84,17 +84,16 @@ impl WindowLike for Terminal {
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![
DrawInstructions::Rect([0, 0], self.dimensions, theme_info.alt_background),
//
];
//add the visible lines of text
let end_line = self.actual_line_num + self.get_max_lines();
let mut text_y = WINDOW_TOP_HEIGHT + PADDING;
let mut text_y = PADDING;
for line_num in self.actual_line_num..end_line {
if line_num == self.actual_lines.len() {
break;
}
let line = self.actual_lines[line_num].clone();
instructions.push(DrawInstructions::Text([PADDING, text_y], "times-new-roman", line, theme_info.alt_text, theme_info.alt_background, Some(MONO_WIDTH)));
instructions.push(DrawInstructions::Text([PADDING, text_y], "times-new-romono", line, theme_info.alt_text, theme_info.alt_background, Some(0), Some(MONO_WIDTH)));
text_y += LINE_HEIGHT;
}
instructions
@@ -109,7 +108,7 @@ impl WindowLike for Terminal {
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[410, 410 + WINDOW_TOP_HEIGHT]
[410, 410]
}
fn resizable(&self) -> bool {
@@ -123,7 +122,7 @@ impl Terminal {
}
fn get_max_lines(&self) -> usize {
(self.dimensions[1] - WINDOW_TOP_HEIGHT- PADDING * 2) / LINE_HEIGHT
(self.dimensions[1] - PADDING * 2) / LINE_HEIGHT
}
fn process_command(&mut self) -> CommandResponse {
@@ -177,13 +176,12 @@ impl Terminal {
//cannot index or do .len() because those count bytes not characters
loop {
if working_line.chars().count() <= max_chars_per_line {
self.actual_lines.push(working_line);
break;
} else {
let mut working_line_chars = working_line.chars();
let mut push_string = String::new();
for i in 0..max_chars_per_line {
for _i in 0..max_chars_per_line {
push_string += &working_line_chars.next().unwrap().to_string();
}
self.actual_lines.push(push_string);

View File

@@ -44,9 +44,9 @@ impl WindowLike for WorkspaceIndicator {
for w in 0..9 {
if w == self.current_workspace as usize {
instructions.push(DrawInstructions::Rect([w * WIDTH, 0], [WIDTH, self.dimensions[1]], theme_info.top));
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text_top, theme_info.top, None));
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.top_text, theme_info.top, None, None));
} else {
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text, theme_info.background, None));
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text, theme_info.background, None, None));
}
}
instructions

View File

@@ -1,6 +1,6 @@
use std::vec::Vec;
use std::vec;
use std::collections::HashMap;
use std::collections::{ HashMap, VecDeque };
use std::fmt;
use std::boxed::Box;
use std::sync::{ LazyLock, Mutex };
@@ -24,10 +24,13 @@ use crate::messages::*;
use crate::window_likes::start_menu::StartMenu;
use crate::window_likes::minesweeper::Minesweeper;
use crate::window_likes::terminal::Terminal;
use crate::window_likes::malvim::Malvim;
//todo, better error handling for windows
pub const TASKBAR_HEIGHT: usize = 38;
pub const INDICATOR_HEIGHT: usize = 20;
pub const WINDOW_TOP_HEIGHT: usize = 26;
const WINDOW_TOP_HEIGHT: usize = 26;
static WRITER: LazyLock<Mutex<FramebufferWriter>> = LazyLock::new(|| Mutex::new(Default::default()));
@@ -48,7 +51,9 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
for c in stdin.keys() {
if let Some(kc) = key_to_char(c.unwrap()) {
//do not allow exit when locked unless debugging
if kc == KeyChar::Alt('e') {
//if kc == KeyChar::Alt('e') && !wm.locked {
write!(stdout, "{}", cursor::Show).unwrap();
stdout.suspend_raw_mode().unwrap();
exit(0);
@@ -68,7 +73,7 @@ pub fn min(one: usize, two: usize) -> usize {
#[derive(Debug)]
pub enum DrawInstructions {
Rect(Point, Dimensions, RGBColor),
Text(Point, &'static str, String, RGBColor, RGBColor, Option<u8>), //font and text
Text(Point, &'static str, String, RGBColor, RGBColor, Option<usize>, Option<u8>), //font and text
Gradient(Point, Dimensions, RGBColor, RGBColor, usize),
Mingde(Point),
}
@@ -126,7 +131,7 @@ pub struct WindowManager {
dimensions: Dimensions,
theme: Themes,
focused_id: usize,
locked: bool,
pub locked: bool,
current_workspace: u8,
framebuffer: Framebuffer,
}
@@ -156,6 +161,7 @@ impl WindowManager {
let id = self.id_count;
self.focused_id = id;
window_like.handle_message(WindowMessage::Init(dimensions));
let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
self.window_infos.push(WindowLikeInfo {
id,
window_like,
@@ -242,11 +248,13 @@ impl WindowManager {
if !self.locked {
//keyboard shortcut
let shortcuts = HashMap::from([
//alt+e is terminate program (ctrl+c)
('s', ShortcutType::StartMenu),
(']', ShortcutType::FocusNextWindow),
('q', ShortcutType::QuitWindow),
('c', ShortcutType::CenterWindow),
('f', ShortcutType::FullscreenWindow),
('w', ShortcutType::HalfWidthWindow),
//move window a small amount
('h', ShortcutType::MoveWindow(Direction::Left)),
('j', ShortcutType::MoveWindow(Direction::Down)),
@@ -422,11 +430,14 @@ impl WindowManager {
} else {
new_dimensions = self.window_infos[focused_index].dimensions;
}
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions(new_dimensions));
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
press_response = WindowMessageResponse::JustRerender;
}
}
}
},
&ShortcutType::HalfWidthWindow => {
//
},
};
}
}
@@ -471,6 +482,7 @@ impl WindowManager {
}
let w: WindowBox = match w {
"Minesweeper" => Box::new(Minesweeper::new()),
"Malvim" => Box::new(Malvim::new()),
"Terminal" => Box::new(Terminal::new()),
"StartMenu" => Box::new(StartMenu::new()),
_ => panic!("no such window"),
@@ -510,6 +522,10 @@ impl WindowManager {
};
}
fn get_true_top_left(top_left: &Point, is_window: bool) -> Point {
[top_left[0], top_left[1] + if is_window { WINDOW_TOP_HEIGHT } else { 0 }]
}
//another issue with a huge vector of draw instructions; it takes up heap memory
pub fn render(&mut self, maybe_redraw_ids: Option<Vec<usize>>, use_saved_buffer: bool) {
let theme_info = get_theme_info(&self.theme).unwrap();
@@ -540,17 +556,24 @@ impl WindowManager {
} else {
window_info.dimensions
};
let mut instructions = Vec::new();
if window_info.window_like.subtype() == WindowLikeType::Window {
let mut instructions = VecDeque::from(window_info.window_like.draw(&theme_info));
let is_window = window_info.window_like.subtype() == WindowLikeType::Window;
if is_window {
//if this is the top most window to draw, snapshot
if w_index == max_index && !use_saved_buffer && redraw_ids.len() == 0 {
WRITER.lock().unwrap().save_buffer();
}
//offset top left by the window top height for windows (because windows can't draw in that region)
instructions = instructions.iter().map(|instruction| {
match instruction {
DrawInstructions::Rect(top_left, dimensions, color) => DrawInstructions::Rect(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *color),
DrawInstructions::Text(top_left, font_name, text, color, bg_color, horiz_spacing, mono_width) => DrawInstructions::Text(WindowManager::get_true_top_left(top_left, is_window), font_name, text.clone(), *color, *bg_color, *horiz_spacing, *mono_width),
DrawInstructions::Mingde(top_left) => DrawInstructions::Mingde(WindowManager::get_true_top_left(top_left, is_window)),
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => DrawInstructions::Gradient(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *start_color, *end_color, *steps),
}
}).collect();
//draw window background
instructions.push(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
}
instructions.extend(window_info.window_like.draw(&theme_info));
if window_info.window_like.subtype() == WindowLikeType::Window {
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
//draw window top decorations and what not
instructions.extend(vec![
//left top border
@@ -558,7 +581,7 @@ impl WindowManager {
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
//top
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
DrawInstructions::Text([4, 4], "times-new-roman", window_info.window_like.title().to_string(), theme_info.text_top, theme_info.top, None),
DrawInstructions::Text([4, 4], "times-new-roman", window_info.window_like.title().to_string(), theme_info.top_text, theme_info.top, None, None),
//top bottom border
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
//right bottom border
@@ -587,8 +610,8 @@ impl WindowManager {
];
window_writer.draw_rect(top_left, true_dimensions, color);
},
DrawInstructions::Text(top_left, font_name, text, color, bg_color, mono_width) => {
window_writer.draw_text(top_left, font_name, &text, color, bg_color, 1, mono_width);
DrawInstructions::Text(top_left, font_name, text, color, bg_color, horiz_spacing, mono_width) => {
window_writer.draw_text(top_left, font_name, &text, color, bg_color, horiz_spacing.unwrap_or(1), mono_width);
},
DrawInstructions::Mingde(top_left) => {
window_writer._draw_mingde(top_left);