commit f595d4f43c46cedeaca90829ece4976da424ba36 Author: stjet <49297268+stjet@users.noreply.github.com> Date: Sat Oct 12 06:18:05 2024 +0000 ported ming-os graphics to linux framebuffer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ad6e375 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ming-os-kernel" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +blake2 = { version = "0.10.6", default-features = false } +linux_framebuffer = { package = "framebuffer", version = "0.3.1" } +bmp-rust = "0.4.1" diff --git a/bmps/mingde.bmp b/bmps/mingde.bmp new file mode 100644 index 0000000..c2986cd Binary files /dev/null and b/bmps/mingde.bmp differ diff --git a/bmps/times-new-roman/!0.bmp b/bmps/times-new-roman/!0.bmp new file mode 100644 index 0000000..1a31840 Binary files /dev/null and b/bmps/times-new-roman/!0.bmp differ diff --git "a/bmps/times-new-roman/\"0.bmp" "b/bmps/times-new-roman/\"0.bmp" new file mode 100644 index 0000000..45b4972 Binary files /dev/null and "b/bmps/times-new-roman/\"0.bmp" differ diff --git a/bmps/times-new-roman/$2.bmp b/bmps/times-new-roman/$2.bmp new file mode 100644 index 0000000..b8c3b7c Binary files /dev/null and b/bmps/times-new-roman/$2.bmp differ diff --git a/bmps/times-new-roman/(0.bmp b/bmps/times-new-roman/(0.bmp new file mode 100644 index 0000000..07a6a62 Binary files /dev/null and b/bmps/times-new-roman/(0.bmp differ diff --git a/bmps/times-new-roman/)0.bmp b/bmps/times-new-roman/)0.bmp new file mode 100644 index 0000000..ef5edf7 Binary files /dev/null and b/bmps/times-new-roman/)0.bmp differ diff --git a/bmps/times-new-roman/*0.bmp b/bmps/times-new-roman/*0.bmp new file mode 100644 index 0000000..05f21b4 Binary files /dev/null and b/bmps/times-new-roman/*0.bmp differ diff --git a/bmps/times-new-roman/,9.bmp b/bmps/times-new-roman/,9.bmp new file mode 100644 index 0000000..949d3e9 Binary files /dev/null and b/bmps/times-new-roman/,9.bmp differ diff --git a/bmps/times-new-roman/-8.bmp b/bmps/times-new-roman/-8.bmp new file mode 100644 index 0000000..6631a22 Binary files /dev/null and b/bmps/times-new-roman/-8.bmp differ diff --git a/bmps/times-new-roman/.9.bmp b/bmps/times-new-roman/.9.bmp new file mode 100644 index 0000000..665aada Binary files /dev/null and b/bmps/times-new-roman/.9.bmp differ diff --git a/bmps/times-new-roman/00.bmp b/bmps/times-new-roman/00.bmp new file mode 100644 index 0000000..0a297ff Binary files /dev/null and b/bmps/times-new-roman/00.bmp differ diff --git a/bmps/times-new-roman/10.bmp b/bmps/times-new-roman/10.bmp new file mode 100644 index 0000000..22a4f15 Binary files /dev/null and b/bmps/times-new-roman/10.bmp differ diff --git a/bmps/times-new-roman/20.bmp b/bmps/times-new-roman/20.bmp new file mode 100644 index 0000000..ba52be3 Binary files /dev/null and b/bmps/times-new-roman/20.bmp differ diff --git a/bmps/times-new-roman/30.bmp b/bmps/times-new-roman/30.bmp new file mode 100644 index 0000000..605f798 Binary files /dev/null and b/bmps/times-new-roman/30.bmp differ diff --git a/bmps/times-new-roman/40.bmp b/bmps/times-new-roman/40.bmp new file mode 100644 index 0000000..d6782f2 Binary files /dev/null and b/bmps/times-new-roman/40.bmp differ diff --git a/bmps/times-new-roman/50.bmp b/bmps/times-new-roman/50.bmp new file mode 100644 index 0000000..6f33bc6 Binary files /dev/null and b/bmps/times-new-roman/50.bmp differ diff --git a/bmps/times-new-roman/60.bmp b/bmps/times-new-roman/60.bmp new file mode 100644 index 0000000..8bb175e Binary files /dev/null and b/bmps/times-new-roman/60.bmp differ diff --git a/bmps/times-new-roman/70.bmp b/bmps/times-new-roman/70.bmp new file mode 100644 index 0000000..3ab4e73 Binary files /dev/null and b/bmps/times-new-roman/70.bmp differ diff --git a/bmps/times-new-roman/80.bmp b/bmps/times-new-roman/80.bmp new file mode 100644 index 0000000..d2a0f31 Binary files /dev/null and b/bmps/times-new-roman/80.bmp differ diff --git a/bmps/times-new-roman/90.bmp b/bmps/times-new-roman/90.bmp new file mode 100644 index 0000000..cc6a555 Binary files /dev/null and b/bmps/times-new-roman/90.bmp differ diff --git a/bmps/times-new-roman/:4.bmp b/bmps/times-new-roman/:4.bmp new file mode 100644 index 0000000..fe83551 Binary files /dev/null and b/bmps/times-new-roman/:4.bmp differ diff --git a/bmps/times-new-roman/?0.bmp b/bmps/times-new-roman/?0.bmp new file mode 100644 index 0000000..cfb9e10 Binary files /dev/null and b/bmps/times-new-roman/?0.bmp differ diff --git a/bmps/times-new-roman/A0.bmp b/bmps/times-new-roman/A0.bmp new file mode 100644 index 0000000..0532917 Binary files /dev/null and b/bmps/times-new-roman/A0.bmp differ diff --git a/bmps/times-new-roman/B1.bmp b/bmps/times-new-roman/B1.bmp new file mode 100644 index 0000000..cc88669 Binary files /dev/null and b/bmps/times-new-roman/B1.bmp differ diff --git a/bmps/times-new-roman/C0.bmp b/bmps/times-new-roman/C0.bmp new file mode 100644 index 0000000..b677184 Binary files /dev/null and b/bmps/times-new-roman/C0.bmp differ diff --git a/bmps/times-new-roman/D1.bmp b/bmps/times-new-roman/D1.bmp new file mode 100644 index 0000000..d68b91a Binary files /dev/null and b/bmps/times-new-roman/D1.bmp differ diff --git a/bmps/times-new-roman/E1.bmp b/bmps/times-new-roman/E1.bmp new file mode 100644 index 0000000..41558b3 Binary files /dev/null and b/bmps/times-new-roman/E1.bmp differ diff --git a/bmps/times-new-roman/F1.bmp b/bmps/times-new-roman/F1.bmp new file mode 100644 index 0000000..86622f4 Binary files /dev/null and b/bmps/times-new-roman/F1.bmp differ diff --git a/bmps/times-new-roman/G0.bmp b/bmps/times-new-roman/G0.bmp new file mode 100644 index 0000000..209d367 Binary files /dev/null and b/bmps/times-new-roman/G0.bmp differ diff --git a/bmps/times-new-roman/H1.bmp b/bmps/times-new-roman/H1.bmp new file mode 100644 index 0000000..e657b2b Binary files /dev/null and b/bmps/times-new-roman/H1.bmp differ diff --git a/bmps/times-new-roman/I1.bmp b/bmps/times-new-roman/I1.bmp new file mode 100644 index 0000000..c048c29 Binary files /dev/null and b/bmps/times-new-roman/I1.bmp differ diff --git a/bmps/times-new-roman/J0.bmp b/bmps/times-new-roman/J0.bmp new file mode 100644 index 0000000..08ea34e Binary files /dev/null and b/bmps/times-new-roman/J0.bmp differ diff --git a/bmps/times-new-roman/K1.bmp b/bmps/times-new-roman/K1.bmp new file mode 100644 index 0000000..c4615d0 Binary files /dev/null and b/bmps/times-new-roman/K1.bmp differ diff --git a/bmps/times-new-roman/L1.bmp b/bmps/times-new-roman/L1.bmp new file mode 100644 index 0000000..ebf012f Binary files /dev/null and b/bmps/times-new-roman/L1.bmp differ diff --git a/bmps/times-new-roman/M1.bmp b/bmps/times-new-roman/M1.bmp new file mode 100644 index 0000000..f978c3b Binary files /dev/null and b/bmps/times-new-roman/M1.bmp differ diff --git a/bmps/times-new-roman/N0.bmp b/bmps/times-new-roman/N0.bmp new file mode 100644 index 0000000..ec59ed3 Binary files /dev/null and b/bmps/times-new-roman/N0.bmp differ diff --git a/bmps/times-new-roman/O0.bmp b/bmps/times-new-roman/O0.bmp new file mode 100644 index 0000000..0f988ad Binary files /dev/null and b/bmps/times-new-roman/O0.bmp differ diff --git a/bmps/times-new-roman/P1.bmp b/bmps/times-new-roman/P1.bmp new file mode 100644 index 0000000..fa63701 Binary files /dev/null and b/bmps/times-new-roman/P1.bmp differ diff --git a/bmps/times-new-roman/Q0.bmp b/bmps/times-new-roman/Q0.bmp new file mode 100644 index 0000000..d1a5367 Binary files /dev/null and b/bmps/times-new-roman/Q0.bmp differ diff --git a/bmps/times-new-roman/R1.bmp b/bmps/times-new-roman/R1.bmp new file mode 100644 index 0000000..9eca796 Binary files /dev/null and b/bmps/times-new-roman/R1.bmp differ diff --git a/bmps/times-new-roman/S0.bmp b/bmps/times-new-roman/S0.bmp new file mode 100644 index 0000000..e10d89d Binary files /dev/null and b/bmps/times-new-roman/S0.bmp differ diff --git a/bmps/times-new-roman/T1.bmp b/bmps/times-new-roman/T1.bmp new file mode 100644 index 0000000..9abd78b Binary files /dev/null and b/bmps/times-new-roman/T1.bmp differ diff --git a/bmps/times-new-roman/U0.bmp b/bmps/times-new-roman/U0.bmp new file mode 100644 index 0000000..5e0358d Binary files /dev/null and b/bmps/times-new-roman/U0.bmp differ diff --git a/bmps/times-new-roman/V0.bmp b/bmps/times-new-roman/V0.bmp new file mode 100644 index 0000000..31e1946 Binary files /dev/null and b/bmps/times-new-roman/V0.bmp differ diff --git a/bmps/times-new-roman/W0.bmp b/bmps/times-new-roman/W0.bmp new file mode 100644 index 0000000..dabd3c1 Binary files /dev/null and b/bmps/times-new-roman/W0.bmp differ diff --git a/bmps/times-new-roman/X1.bmp b/bmps/times-new-roman/X1.bmp new file mode 100644 index 0000000..0f36ca7 Binary files /dev/null and b/bmps/times-new-roman/X1.bmp differ diff --git a/bmps/times-new-roman/Y1.bmp b/bmps/times-new-roman/Y1.bmp new file mode 100644 index 0000000..6bee7e6 Binary files /dev/null and b/bmps/times-new-roman/Y1.bmp differ diff --git a/bmps/times-new-roman/Z1.bmp b/bmps/times-new-roman/Z1.bmp new file mode 100644 index 0000000..9f2ee00 Binary files /dev/null and b/bmps/times-new-roman/Z1.bmp differ diff --git a/bmps/times-new-roman/a4.bmp b/bmps/times-new-roman/a4.bmp new file mode 100644 index 0000000..6ce75a2 Binary files /dev/null and b/bmps/times-new-roman/a4.bmp differ diff --git a/bmps/times-new-roman/b0.bmp b/bmps/times-new-roman/b0.bmp new file mode 100644 index 0000000..7aa6910 Binary files /dev/null and b/bmps/times-new-roman/b0.bmp differ diff --git a/bmps/times-new-roman/c4.bmp b/bmps/times-new-roman/c4.bmp new file mode 100644 index 0000000..4b01cb0 Binary files /dev/null and b/bmps/times-new-roman/c4.bmp differ diff --git a/bmps/times-new-roman/d0.bmp b/bmps/times-new-roman/d0.bmp new file mode 100644 index 0000000..7038199 Binary files /dev/null and b/bmps/times-new-roman/d0.bmp differ diff --git a/bmps/times-new-roman/e4.bmp b/bmps/times-new-roman/e4.bmp new file mode 100644 index 0000000..ec69414 Binary files /dev/null and b/bmps/times-new-roman/e4.bmp differ diff --git a/bmps/times-new-roman/f0.bmp b/bmps/times-new-roman/f0.bmp new file mode 100644 index 0000000..c305bf7 Binary files /dev/null and b/bmps/times-new-roman/f0.bmp differ diff --git a/bmps/times-new-roman/g4.bmp b/bmps/times-new-roman/g4.bmp new file mode 100644 index 0000000..91e8dcb Binary files /dev/null and b/bmps/times-new-roman/g4.bmp differ diff --git a/bmps/times-new-roman/h0.bmp b/bmps/times-new-roman/h0.bmp new file mode 100644 index 0000000..c34b689 Binary files /dev/null and b/bmps/times-new-roman/h0.bmp differ diff --git a/bmps/times-new-roman/i0.bmp b/bmps/times-new-roman/i0.bmp new file mode 100644 index 0000000..1060f09 Binary files /dev/null and b/bmps/times-new-roman/i0.bmp differ diff --git a/bmps/times-new-roman/j2.bmp b/bmps/times-new-roman/j2.bmp new file mode 100644 index 0000000..4ad08f8 Binary files /dev/null and b/bmps/times-new-roman/j2.bmp differ diff --git a/bmps/times-new-roman/k0.bmp b/bmps/times-new-roman/k0.bmp new file mode 100644 index 0000000..588eda3 Binary files /dev/null and b/bmps/times-new-roman/k0.bmp differ diff --git a/bmps/times-new-roman/l0.bmp b/bmps/times-new-roman/l0.bmp new file mode 100644 index 0000000..a41c814 Binary files /dev/null and b/bmps/times-new-roman/l0.bmp differ diff --git a/bmps/times-new-roman/m4.bmp b/bmps/times-new-roman/m4.bmp new file mode 100644 index 0000000..b69c930 Binary files /dev/null and b/bmps/times-new-roman/m4.bmp differ diff --git a/bmps/times-new-roman/n4.bmp b/bmps/times-new-roman/n4.bmp new file mode 100644 index 0000000..454b977 Binary files /dev/null and b/bmps/times-new-roman/n4.bmp differ diff --git a/bmps/times-new-roman/o4.bmp b/bmps/times-new-roman/o4.bmp new file mode 100644 index 0000000..4c16844 Binary files /dev/null and b/bmps/times-new-roman/o4.bmp differ diff --git a/bmps/times-new-roman/p4.bmp b/bmps/times-new-roman/p4.bmp new file mode 100644 index 0000000..caf0de1 Binary files /dev/null and b/bmps/times-new-roman/p4.bmp differ diff --git a/bmps/times-new-roman/q4.bmp b/bmps/times-new-roman/q4.bmp new file mode 100644 index 0000000..5f79f69 Binary files /dev/null and b/bmps/times-new-roman/q4.bmp differ diff --git a/bmps/times-new-roman/r4.bmp b/bmps/times-new-roman/r4.bmp new file mode 100644 index 0000000..15375e0 Binary files /dev/null and b/bmps/times-new-roman/r4.bmp differ diff --git a/bmps/times-new-roman/s4.bmp b/bmps/times-new-roman/s4.bmp new file mode 100644 index 0000000..5eccdbc Binary files /dev/null and b/bmps/times-new-roman/s4.bmp differ diff --git a/bmps/times-new-roman/t2.bmp b/bmps/times-new-roman/t2.bmp new file mode 100644 index 0000000..4db29b7 Binary files /dev/null and b/bmps/times-new-roman/t2.bmp differ diff --git a/bmps/times-new-roman/u4.bmp b/bmps/times-new-roman/u4.bmp new file mode 100644 index 0000000..5c54ad1 Binary files /dev/null and b/bmps/times-new-roman/u4.bmp differ diff --git a/bmps/times-new-roman/v4.bmp b/bmps/times-new-roman/v4.bmp new file mode 100644 index 0000000..288869c Binary files /dev/null and b/bmps/times-new-roman/v4.bmp differ diff --git a/bmps/times-new-roman/w4.bmp b/bmps/times-new-roman/w4.bmp new file mode 100644 index 0000000..a9559e4 Binary files /dev/null and b/bmps/times-new-roman/w4.bmp differ diff --git a/bmps/times-new-roman/x4.bmp b/bmps/times-new-roman/x4.bmp new file mode 100644 index 0000000..1b58d2c Binary files /dev/null and b/bmps/times-new-roman/x4.bmp differ diff --git a/bmps/times-new-roman/y4.bmp b/bmps/times-new-roman/y4.bmp new file mode 100644 index 0000000..92d018a Binary files /dev/null and b/bmps/times-new-roman/y4.bmp differ diff --git a/bmps/times-new-roman/z4.bmp b/bmps/times-new-roman/z4.bmp new file mode 100644 index 0000000..959bbbe Binary files /dev/null and b/bmps/times-new-roman/z4.bmp differ diff --git a/bmps/times-new-roman/{0.bmp b/bmps/times-new-roman/{0.bmp new file mode 100644 index 0000000..1cbd65a Binary files /dev/null and b/bmps/times-new-roman/{0.bmp differ diff --git a/bmps/times-new-roman/}0.bmp b/bmps/times-new-roman/}0.bmp new file mode 100644 index 0000000..71a17d0 Binary files /dev/null and b/bmps/times-new-roman/}0.bmp differ diff --git a/bmps/times-new-roman/█1.bmp b/bmps/times-new-roman/█1.bmp new file mode 100644 index 0000000..fd27b34 Binary files /dev/null and b/bmps/times-new-roman/█1.bmp differ diff --git a/src/components/highlight_button.rs b/src/components/highlight_button.rs new file mode 100644 index 0000000..516c8fc --- /dev/null +++ b/src/components/highlight_button.rs @@ -0,0 +1,78 @@ +use std::vec; +use std::vec::Vec; + +use crate::components::Component; +use crate::framebuffer::{ Dimensions, Point }; +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::DrawInstructions; + +pub struct HighlightButton { + name_: String, + top_left: Point, + size: Dimensions, + text: &'static str, + pub highlighted: bool, + click_return: T, + toggle_highlight_return: T, //also unhighlight return +} + +impl Component for HighlightButton { + fn handle_message(&mut self, message: WindowMessage) -> Option { + match message { + WindowMessage::Focus | WindowMessage::Unfocus => { + self.highlighted = !self.highlighted; + Some(self.toggle_highlight_return.clone()) + }, + WindowMessage::FocusClick => { + //we know this click was for this button, otherwise window wouldn't have given us this message + Some(self.click_return.clone()) + }, + _ => None, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let font_height = 15; + if self.highlighted { + 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), + ] + } 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), + ] + } + } + + //properties + fn focusable(&self) -> bool { + true + } + + fn clickable(&self) -> bool { + true + } + + fn name(&self) -> &String { + &self.name_ + } +} + +impl HighlightButton { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, toggle_highlight_return: T, highlighted: bool) -> Self { + Self { + name_, + top_left, + size, + text, + click_return, + toggle_highlight_return, + highlighted, + } + } +} + diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..ed15e7c --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,21 @@ +use std::vec::Vec; + +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::DrawInstructions; + +pub mod toggle_button; +pub mod highlight_button; + +pub trait Component { + fn handle_message(&mut self, message: WindowMessage) -> Option; + fn draw(&self, theme_info: &ThemeInfo) -> Vec; + + //properties + //focusing is a way for the *window* to know what component to send input, presses, etc + //focusing for components is purely to give a visual representation + fn focusable(&self) -> bool; + fn clickable(&self) -> bool; + fn name(&self) -> &String; //should be unique +} + diff --git a/src/components/toggle_button.rs b/src/components/toggle_button.rs new file mode 100644 index 0000000..5368df2 --- /dev/null +++ b/src/components/toggle_button.rs @@ -0,0 +1,90 @@ +use std::vec; +use std::vec::Vec; + +use crate::components::Component; +use crate::framebuffer::{ Dimensions, Point }; +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::DrawInstructions; + +//we need a text width and height measure function first +pub enum ToggleButtonAlignment { + Centre, + Left, +} + +pub struct ToggleButton { + name_: String, + top_left: Point, + size: Dimensions, + text: &'static str, + draw_bg: bool, + pub inverted: bool, //whether is it clicked or not + alignment: ToggleButtonAlignment, + click_return: T, + unclick_return: T, +} + +impl Component for ToggleButton { + fn handle_message(&mut self, message: WindowMessage) -> Option { + match message { + WindowMessage::FocusClick => { + //we know this click was for this button, otherwise window wouldn't have given us this message + self.inverted = !self.inverted; + if self.inverted { + Some(self.click_return.clone()) + } else { + Some(self.unclick_return.clone()) + } + }, + _ => None, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + //to make sure the text gets vertically centred + let font_height = 15; + vec![ + //top and left border + DrawInstructions::Rect(self.top_left, [self.size[0], 2], if self.inverted { theme_info.border_right_bottom } else { theme_info.border_left_top }), + DrawInstructions::Rect(self.top_left, [2, self.size[1]], if self.inverted { theme_info.border_right_bottom } else { theme_info.border_left_top }), + //right and bottom border + DrawInstructions::Rect([self.top_left[0] + self.size[0] - 2, self.top_left[1]], [2, self.size[1]], if self.inverted { theme_info.border_left_top } else { theme_info.border_right_bottom }), + DrawInstructions::Rect([self.top_left[0], self.top_left[1] + self.size[1] - 2], [self.size[0], 2], if self.inverted { theme_info.border_left_top } else { theme_info.border_right_bottom }), + //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), + ] + } + + //properties + fn focusable(&self) -> bool { + true + } + + fn clickable(&self) -> bool { + true + } + + fn name(&self) -> &String { + &self.name_ + } +} + +impl ToggleButton { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, unclick_return: T, draw_bg: bool, alignment: Option) -> Self { + Self { + name_, + top_left, + size, + text, + click_return, + unclick_return, + draw_bg, + inverted: false, + alignment: alignment.unwrap_or(ToggleButtonAlignment::Centre), + } + } +} + diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 0000000..31872a4 --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,228 @@ +use std::vec::Vec; +use core::ptr; + +use crate::fs::{ get_font_char, get_bmp }; + +pub type Point = [usize; 2]; //x, y +pub type Dimensions = [usize; 2]; //width, height +pub type RGBColor = [u8; 3]; //rgb + +type FontChar = (char, Vec>, u8); + +fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor { + /*let factor: f32 = alpha as f32 / 255.0; + [ + (bg_color[0] as f32 * (1.0 - factor)) as u8 + (color[0] as f32 * factor) as u8, + (bg_color[1] as f32 * (1.0 - factor)) as u8 + (color[1] as f32 * factor) as u8, + (bg_color[2] as f32 * (1.0 - factor)) as u8 + (color[2] as f32 * factor) as u8, + ]*/ + //255 * 255 < max(u16) + let alpha = alpha as u16; + [ + (bg_color[0] as u16 * (255 - alpha) / 255) as u8 + (color[0] as u16 * alpha / 255) as u8, + (bg_color[1] as u16 * (255 - alpha) / 255) as u8 + (color[1] as u16 * alpha / 255) as u8, + (bg_color[2] as u16 * (255 - alpha) / 255) as u8 + (color[2] as u16 * alpha / 255) as u8, + ] +} + +#[derive(Clone, Default, Debug)] +pub struct FramebufferInfo { + pub byte_len: usize, + pub width: usize, + pub height: usize, + pub bytes_per_pixel: usize, + pub stride: usize, +} + +//currently doesn't check if writing onto next line accidentally +pub struct FramebufferWriter { + info: FramebufferInfo, + buffer: Vec, + saved_buffer: Option>, +} + +impl FramebufferWriter { + pub fn init(&mut self, info: FramebufferInfo, buffer: Vec) { + self.info = info; + self.buffer = buffer; + } + + pub fn get_info(&self) -> FramebufferInfo { + self.info.clone() + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + + pub fn save_buffer(&mut self) { + self.saved_buffer = Some(self.buffer.clone()); + } + + pub fn write_saved_buffer_to_raw(&mut self) { + self.buffer[..] + .copy_from_slice(&self.saved_buffer.as_ref().unwrap()[..]); + } + + fn _draw_pixel(&mut self, start_pos: usize, color: RGBColor) { + self.buffer[start_pos..(start_pos + 3)] + .copy_from_slice(&color[..]); + } + + fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) { + self.buffer[start_pos..(start_pos + bytes.len())] + .copy_from_slice(bytes); + } + + pub fn draw_buffer(&mut self, top_left: Point, height: usize, bytes_per_line: usize, bytes: &[u8]) { + //for our framebuffer + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + //of the buffer we want to draw on + let mut start = 0; + for _y in 0..height { + self.buffer[start_pos..(start_pos + bytes_per_line)] + .copy_from_slice(&bytes[start..(start + bytes_per_line)]); + let _ = unsafe { ptr::read_volatile(&self.buffer[start_pos]) }; + start += bytes_per_line; + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + + pub fn draw_char(&mut self, top_left: Point, char_info: &FontChar, 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] { + if col > &0 { + self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col)); + } + start_pos += self.info.bytes_per_pixel; + } + } + } + + //dots + + pub fn draw_pixel(&mut self, point: Point, color: RGBColor) { + let start_pos = (point[1] * self.info.stride + point[0]) * self.info.bytes_per_pixel; + self._draw_pixel(start_pos, color); + } + + //(lines are rectangles of height 1) + pub fn draw_line(&mut self, left: Point, width: usize, color: RGBColor) { + self.draw_rect(left, [width, 1], color); + } + + //shapes + + pub fn draw_rect(&mut self, top_left: Point, dimensions: Dimensions, color: RGBColor) { + let line_bytes = if self.info.bytes_per_pixel > 3 { + [color[0], color[1], color[2], 255].repeat(dimensions[0]) + } else { + color.repeat(dimensions[0]) + }; + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for _row in 0..dimensions[1] { + /* + * for _col in 0..dimensions[0] { + self._draw_pixel(start_pos, color); + start_pos += self.info.bytes_per_pixel; + } + //assumes stride is same as bytes_per_pixel * width + //start_pos = start_pos + top_left[0] * self.info.bytes_per_pixel; + */ + //use _draw_line instead for MUCH more efficiency + self._draw_line(start_pos, &line_bytes[..]); + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + + //direction is top to bottom + pub fn draw_gradient(&mut self, top_left: Point, dimensions: Dimensions, start_color: RGBColor, end_color: RGBColor, steps: usize) { + let delta_r = (end_color[0] as f32 - start_color[0] as f32) / steps as f32; + let delta_g = (end_color[1] as f32 - start_color[1] as f32) / steps as f32; + let delta_b = (end_color[2] as f32 - start_color[2] as f32) / steps as f32; + let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + if steps <= dimensions[1] { + //rounds down + let mut y_per = dimensions[1] / steps; + for s in 0..steps { + let color; + if s == steps - 1 { + color = end_color; + //the remaining lines are the last one + y_per = dimensions[1] - (y_per * steps); + } else { + color = [(start_color[0] as f32 + (delta_r * s as f32)) as u8, (start_color[1] as f32 + (delta_g * s as f32)) as u8, (start_color[2] as f32 + (delta_b * s as f32)) as u8]; + }; + let line_bytes = if self.info.bytes_per_pixel > 3 { + [color[0], color[1], color[2], 255].repeat(dimensions[0]) + } else { + color.repeat(dimensions[0]) + }; + for _y in 0..y_per { + self._draw_line(start_pos, &line_bytes[..]); + start_pos += self.info.stride * self.info.bytes_per_pixel; + } + } + } + } + + //text + + pub fn draw_text(&mut self, top_left: Point, font_name: &str, text: &str, color: RGBColor, bg_color: RGBColor, horiz_spacing: usize, mono_width: Option) { + let mut top_left = top_left; + //todo, config space + for c in text.chars() { + if c == ' ' { + top_left[0] += 5; + } 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 + }; + 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; + } + } + } + } + + //bmps + + pub fn _draw_mingde(&mut self, top_left: Point) { + let mut start_pos; + let mingde = get_bmp("./bmps/mingde.bmp"); + for row in 0..mingde.len() { + start_pos = ((top_left[1] + row) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; + for color in &mingde[row] { + self._draw_pixel(start_pos, [color[0], color[1], color[2]]); + start_pos += self.info.bytes_per_pixel; + } + } + } +} + +impl Default for FramebufferWriter { + fn default() -> Self { + Self { + info: Default::default(), + buffer: Vec::new(), + saved_buffer: None, + } + } +} + diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..6f3ae4a --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,52 @@ +use std::fs::read_dir; +use std::path::Path; + +use bmp_rust::bmp::BMP; + +pub fn get_font_char(dir: &str, c: char) -> Option<(char, Vec>, u8)> { + let mut font: Vec<(char, Vec>, u8)> = Vec::new(); + for entry in read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + let path_chars: Vec = path.file_name().unwrap().to_str().unwrap().to_string().chars().collect(); + if path_chars[0] == c { + let mut ch: Vec> = Vec::new(); + if !path.is_dir() { + let b = BMP::new_from_file(&path.clone().into_os_string().into_string().unwrap()); + let dib_header = b.get_dib_header().unwrap(); + let width = dib_header.width as usize; + let height = dib_header.height as usize; + for y in 0..height { + let mut row = Vec::new(); + for x in 0..width { + let pixel_color = b.get_color_of_px(x, y).unwrap(); + //if black, true + row.push(pixel_color[3]); //push alpha channel + } + ch.push(row); + } + return Some((path_chars[0], ch, path_chars[1].to_digit(10).unwrap() as u8)); + } + } + } + None +} + +//the Vec should be [u8; 3] but thats a job for another day +pub fn get_bmp(path: &str) -> Vec>> { + let mut bmp: Vec>> = Vec::new(); + let b = BMP::new_from_file(path); + let dib_header = b.get_dib_header().unwrap(); + let width = dib_header.width as usize; + let height = dib_header.height as usize; + for y in 0..height { + let mut row = Vec::new(); + for x in 0..width { + let pixel_color = b.get_color_of_px(x, y).unwrap(); + //if black, true + row.push(vec![pixel_color[0], pixel_color[1], pixel_color[2]]); //push alpha channel + } + bmp.push(row); + } + bmp +} + diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000..0152a01 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,109 @@ + +#[derive(Clone, Debug)] +pub enum KeyChar { + Press(char), + SpecialPress(&'static str), + SpecialRelease(&'static str), +} + +//use Linear A for escape, backspace, enter +//https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1 +pub fn scancode_to_char(scancode: u8) -> Option { + match scancode { + 0x01 => Some(KeyChar::Press('𐘀')), //escape + 0x02 => Some(KeyChar::Press('1')), + 0x03 => Some(KeyChar::Press('2')), + 0x04 => Some(KeyChar::Press('3')), + 0x05 => Some(KeyChar::Press('4')), + 0x06 => Some(KeyChar::Press('5')), + 0x07 => Some(KeyChar::Press('6')), + 0x08 => Some(KeyChar::Press('7')), + 0x09 => Some(KeyChar::Press('8')), + 0x0A => Some(KeyChar::Press('9')), + 0x0B => Some(KeyChar::Press('0')), + 0x0C => Some(KeyChar::Press('-')), + 0x0D => Some(KeyChar::Press('=')), + 0x0E => Some(KeyChar::Press('𐘁')), //backspace + // + 0x10 => Some(KeyChar::Press('q')), + 0x11 => Some(KeyChar::Press('w')), + 0x12 => Some(KeyChar::Press('e')), + 0x13 => Some(KeyChar::Press('r')), + 0x14 => Some(KeyChar::Press('t')), + 0x15 => Some(KeyChar::Press('y')), + 0x16 => Some(KeyChar::Press('u')), + 0x17 => Some(KeyChar::Press('i')), + 0x18 => Some(KeyChar::Press('o')), + 0x19 => Some(KeyChar::Press('p')), + 0x1A => Some(KeyChar::Press('[')), + 0x1B => Some(KeyChar::Press(']')), + 0x1C => Some(KeyChar::Press('𐘂')), //enter + // + 0x1E => Some(KeyChar::Press('a')), + 0x1F => Some(KeyChar::Press('s')), + 0x20 => Some(KeyChar::Press('d')), + 0x21 => Some(KeyChar::Press('f')), + 0x22 => Some(KeyChar::Press('g')), + 0x23 => Some(KeyChar::Press('h')), + 0x24 => Some(KeyChar::Press('j')), + 0x25 => Some(KeyChar::Press('k')), + 0x26 => Some(KeyChar::Press('l')), + 0x27 => Some(KeyChar::Press(';')), + 0x28 => Some(KeyChar::Press('\'')), + 0x29 => Some(KeyChar::Press('`')), + 0x2A => Some(KeyChar::SpecialPress("shift")), + 0x2B => Some(KeyChar::Press('\\')), + 0x2C => Some(KeyChar::Press('z')), + 0x2D => Some(KeyChar::Press('x')), + 0x2E => Some(KeyChar::Press('c')), + 0x2F => Some(KeyChar::Press('v')), + 0x30 => Some(KeyChar::Press('b')), + 0x31 => Some(KeyChar::Press('n')), + 0x32 => Some(KeyChar::Press('m')), + 0x33 => Some(KeyChar::Press(',')), + 0x34 => Some(KeyChar::Press('.')), + 0x35 => Some(KeyChar::Press('/')), + // + 0x38 => Some(KeyChar::SpecialPress("alt")), + 0x39 => Some(KeyChar::Press(' ')), + // + 0xAA => Some(KeyChar::SpecialRelease("shift")), + // + 0xB8 => Some(KeyChar::SpecialRelease("alt")), + _ => None, + } +} + +//handle shift + key +pub fn uppercase_or_special(c: char) -> char { + let upper = c.to_uppercase().next().unwrap(); + if upper == c { + //special, the other keys on top + match c { + '1' => '!', + '2' => '@', + '3' => '#', + '4' => '$', + '5' => '%', + '6' => '^', + '7' => '&', + '8' => '*', + '9' => '(', + '0' => ')', + '-' => '_', + '=' => '+', + '[' => '{', + ']' => '}', + '\\' => '|', + ';' => ':', + '\'' => '"', + ',' => '<', + '.' => '>', + '/' => '?', + _ => c, + } + } else { + upper + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..852e938 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,37 @@ +use linux_framebuffer::Framebuffer; + +mod framebuffer; +use framebuffer::FramebufferInfo; + +mod window_manager; +use window_manager::init; + +mod window_likes; + +mod components; + +mod themes; + +mod keyboard; + +mod messages; + +mod fs; + +fn main() { + let mut fb = Framebuffer::new("/dev/fb0").unwrap(); + let bytes_per_pixel = (fb.var_screen_info.bits_per_pixel as usize) / 8; + let fb_info = FramebufferInfo { + byte_len: (fb.var_screen_info.yres_virtual * fb.fix_screen_info.line_length) as usize, + width: fb.var_screen_info.xres_virtual as usize, + height: fb.var_screen_info.yres_virtual as usize, + bytes_per_pixel, + stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel, + }; + println!("{:?}", fb_info); + + init(fb, fb_info); + + // +} + diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..2ff7227 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,94 @@ +use std::boxed::Box; +use std::fmt; +use std::vec::Vec; + +use crate::keyboard::KeyChar; +use crate::framebuffer::Dimensions; +use crate::window_manager::WindowLike; + +pub enum WindowManagerMessage { + KeyChar(KeyChar), + // +} + +pub type WindowBox = Box; + +/* +impl PartialEq for WindowBox { + fn eq(&self, _other: &Self) -> bool { + //lol + true + } +} +*/ + +#[derive(PartialEq)] +pub enum WindowManagerRequest { + OpenWindow(&'static str), + CloseStartMenu, + Unlock, + Lock, + // +} + +impl fmt::Debug for WindowManagerRequest{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WindowManagerRequest lmao") + } +} + +#[derive(PartialEq, Debug)] +pub enum WindowMessageResponse { + Request(WindowManagerRequest), + JustRerender, + DoNothing, +} + +pub struct KeyPress { + pub key: char, + pub held_special_keys: Vec<&'static str>, + // +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Direction { + Left, + Down, + Up, + Right, +} + +//todo, rename to CommandType +#[derive(PartialEq)] +pub enum ShortcutType { + StartMenu, + SwitchWorkspace(u8), + MoveWindowToWorkspace(u8), + FocusNextWindow, + QuitWindow, + MoveWindow(Direction), + MoveWindowToEdge(Direction), + CenterWindow, + FullscreenWindow, + // +} + +pub type WindowsVec = Vec<(usize, &'static str)>; + +pub enum InfoType { + //let taskbar know what the current windows in the workspace are + WindowsInWorkspace(WindowsVec, usize), //Vec, focused id + // +} + +pub enum WindowMessage { + Init(Dimensions), + KeyPress(KeyPress), + Shortcut(ShortcutType), + Info(InfoType), + Focus, + Unfocus, + FocusClick, + ChangeDimensions(Dimensions), + // +} diff --git a/src/themes.rs b/src/themes.rs new file mode 100644 index 0000000..7ac1b99 --- /dev/null +++ b/src/themes.rs @@ -0,0 +1,44 @@ +use crate::framebuffer::RGBColor; + +#[derive(PartialEq, Default)] +pub enum Themes { + #[default] + Standard, + // +} + +pub struct ThemeInfo { + pub top: RGBColor, + pub background: RGBColor, + pub border_left_top: RGBColor, + pub border_right_bottom: RGBColor, + pub text: RGBColor, + pub text_top: RGBColor, + pub alt_background: RGBColor, + pub alt_text: RGBColor, + // +} + +const THEME_INFOS: [(Themes, ThemeInfo); 1] = [ + (Themes::Standard, ThemeInfo { + top: [0, 0, 128], + background: [192, 192, 192], + border_left_top: [255, 255, 255], + border_right_bottom: [0, 0, 0], + text: [0, 0, 0], + text_top: [255, 255, 255], + alt_background: [0, 0, 0], + alt_text: [255, 255, 255], + // + }), +]; + +pub fn get_theme_info(theme: &Themes) -> Option { + for pair in THEME_INFOS { + if &pair.0 == theme { + return Some(pair.1); + } + } + return None; +} + diff --git a/src/window_likes/desktop_background.rs b/src/window_likes/desktop_background.rs new file mode 100644 index 0000000..6c58da9 --- /dev/null +++ b/src/window_likes/desktop_background.rs @@ -0,0 +1,44 @@ +use std::vec; +use std::vec::Vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT, INDICATOR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +pub struct DesktopBackground { + dimensions: Dimensions, +} + +impl WindowLike for DesktopBackground { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, _theme_info: &ThemeInfo) -> Vec { + vec![DrawInstructions::Rect([0, 0], self.dimensions, [0, 128, 128])] + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::DesktopBackground + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT] + } +} + +impl DesktopBackground { + pub fn new() -> Self { + Self { dimensions: [0, 0] } + } +} + diff --git a/src/window_likes/lock_screen.rs b/src/window_likes/lock_screen.rs new file mode 100644 index 0000000..76fc895 --- /dev/null +++ b/src/window_likes/lock_screen.rs @@ -0,0 +1,79 @@ +use std::vec; +use std::vec::Vec; + +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use blake2::{ Blake2b512, Digest }; + +const PASSWORD_HASH: [u8; 64] = [220, 88, 183, 188, 240, 27, 107, 181, 58, 191, 198, 170, 114, 38, 7, 148, 6, 179, 75, 128, 231, 171, 172, 220, 85, 38, 36, 113, 116, 146, 70, 197, 163, 179, 158, 192, 130, 53, 247, 48, 47, 209, 95, 96, 179, 211, 4, 122, 254, 127, 21, 165, 139, 199, 151, 226, 216, 176, 123, 41, 194, 221, 58, 69]; + +pub struct LockScreen { + dimensions: Dimensions, + input_password: String, +} + +impl WindowLike for LockScreen { + 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 == '𐘂' { //the enter key + //check password + let mut hasher = Blake2b512::new(); + hasher.update(self.input_password.as_bytes()); + if hasher.finalize() == PASSWORD_HASH.into() { + WindowMessageResponse::Request(WindowManagerRequest::Unlock) + } else { + self.input_password = String::new(); + WindowMessageResponse::JustRerender + } + } else if key_press.key == '𐘁' { //backspace + let p_len = self.input_password.len(); + if p_len != 0 { + self.input_password = self.input_password[..p_len - 1].to_string(); + } + WindowMessageResponse::JustRerender + } else { + self.input_password += &key_press.key.to_string(); + WindowMessageResponse::JustRerender + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, _theme_info: &ThemeInfo) -> Vec { + 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), + ] + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::LockScreen + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + dimensions //fullscreen + } +} + +impl LockScreen { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + input_password: String::new(), + } + } +} + diff --git a/src/window_likes/minesweeper.rs b/src/window_likes/minesweeper.rs new file mode 100644 index 0000000..664f4f2 --- /dev/null +++ b/src/window_likes/minesweeper.rs @@ -0,0 +1,350 @@ +use std::vec::Vec; +use std::vec; +use std::collections::VecDeque; +use core::convert::TryFrom; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const HEX_CHARS: [char; 16] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + +fn u8_to_hex(u: u8) -> String { + let mut h = String::new(); + h.push(HEX_CHARS[(u / 16) as usize]); + h.push(HEX_CHARS[(u % 16) as usize]); + h +} + +fn hex_to_u8(c1: char, c2: char) -> u8 { + (HEX_CHARS.iter().position(|c| c == &c1).unwrap() * 16 + HEX_CHARS.iter().position(|c| c == &c2).unwrap()) as u8 +} + +//16x16 with 40 mines + +#[derive(Default)] +struct MineTile { + mine: bool, + revealed: bool, + touching: u8, +} + +#[derive(Default, PartialEq)] +enum MinesweeperState { + #[default] + Seed, + BeforePlaying, + Playing, + Won, + Lost, +} + +#[derive(Default)] +pub struct Minesweeper { + dimensions: Dimensions, + state: MinesweeperState, + tiles: [[MineTile; 16]; 16], + random_chars: String, + random_seed: u32, //user types in random keyboard stuff at beginning + first_char: char, //defaults to '\0' +} + +impl WindowLike for Minesweeper { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + if self.state == MinesweeperState::Seed { + if self.random_chars.len() == 4 { + let mut r_chars = self.random_chars.chars(); + self.random_seed = ((r_chars.next().unwrap() as u8 as u32) << 24) | ((r_chars.next().unwrap() as u8 as u32) << 16) | ((r_chars.next().unwrap() as u8 as u32) << 8) | (r_chars.next().unwrap() as u8 as u32); + self.random_chars = String::new(); + self.state = MinesweeperState::BeforePlaying; + } else { + if u8::try_from(key_press.key).is_ok() { + self.random_chars.push(key_press.key); + } + } + WindowMessageResponse::JustRerender + } else if self.state == MinesweeperState::BeforePlaying || self.state == MinesweeperState::Playing { + if key_press.key == '𐘁' { //backspace + self.first_char = '\0'; + WindowMessageResponse::DoNothing + } else if self.first_char == '\0' { + if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + self.first_char = key_press.key; + } + WindowMessageResponse::DoNothing + } else if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + let u = hex_to_u8(self.first_char, key_press.key) as usize; + let y = u / 16; + let x = u % 16; + if HEX_CHARS.iter().find(|c| c == &&key_press.key).is_some() { + if self.state == MinesweeperState::BeforePlaying { + loop { + self.new_tiles(); + if self.tiles[y][x].touching == 0 && !self.tiles[y][x].mine { + break; + } + } + } + self.state = MinesweeperState::Playing; + //if that tile not reveal it, reveal it and all adjacent zero touching squares, etc + if self.tiles[y][x].mine { + self.tiles[y][x].revealed = true; + self.state = MinesweeperState::Lost; + } else if self.tiles[y][x].touching == 0 { + let mut queue = VecDeque::new(); + queue.push_back([x, y]); + let mut to_reveal = Vec::new(); + while queue.len() > 0 { + let current = queue.pop_front().unwrap(); + self.on_adjacent_tiles(current[0], current[1], |x2, y2| { + if !queue.contains(&[x2, y2]) && !to_reveal.contains(&[x2, y2]) { + if self.tiles[y2][x2].touching == 0 { + queue.push_back([x2, y2]); + } else { + to_reveal.push([x2, y2]); + } + } + }, false); + to_reveal.push(current); + } + for r in to_reveal { + self.tiles[r[1]][r[0]].revealed = true; + } + } else { + self.tiles[y][x].revealed = true; + } + self.first_char = '\0'; + if self.state != MinesweeperState::Lost { + //check for win + let mut won = true; + for y in 0..16 { + for x in 0..16 { + let tile = &self.tiles[y][x]; + if !tile.revealed && !tile.mine { + won = false; + } + } + } + if won { + self.state = MinesweeperState::Won; + } + } + WindowMessageResponse::JustRerender + } else { + WindowMessageResponse::DoNothing + } + } else { + WindowMessageResponse::DoNothing + } + } else { + self.tiles = Default::default(); + self.state = MinesweeperState::Seed; + WindowMessageResponse::DoNothing + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + 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), + ] + } 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]), + //left border + DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT], [5, self.dimensions[1] - WINDOW_TOP_HEIGHT - 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]), + DrawInstructions::Rect([4, self.dimensions[1] - 5], [1, 1], [128, 128, 128]), + //bottom border + DrawInstructions::Rect([6, self.dimensions[1] - 6], [self.dimensions[0] - 2, 5], [255, 255, 255]), + DrawInstructions::Rect([5, self.dimensions[1] - 5], [1, 4], [255, 255, 255]), + DrawInstructions::Rect([4, self.dimensions[1] - 4], [1, 3], [255, 255, 255]), + 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]), + ]; + let tile_size = (self.dimensions[0] - 10) / 16; + for y in 0..16 { + for x in 0..16 { + 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)); + } else { + let color = match tile.touching { + 1 => [0, 0, 255], + 2 => [0, 255, 0], + 3 => [255, 0, 0], + 4 => [128, 0, 128], + 5 => [176, 48, 96], + 6 => [127, 255, 212], + 7 => [0, 0, 0], + //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)); + } + } else { + let top_left = [x * tile_size + 6, WINDOW_TOP_HEIGHT + 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 + DrawInstructions::Rect([top_left[0], top_left[1]], [tile_size - 3, 3], [255, 255, 255]), + // + //left border + DrawInstructions::Rect([top_left[0], top_left[1]], [3, tile_size - 3], [255, 255, 255]), + // + //bottom border + DrawInstructions::Rect([top_left[0] + 3, top_left[1] + tile_size - 4], [tile_size - 4, 3], [128, 128, 128]), + // + //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), + ]); + } + } + } + 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)]); + } 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 + } + } + + //properties + fn title(&self) -> &'static str { + "Minesweeper" + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [410, 410 + WINDOW_TOP_HEIGHT] + } +} + +impl Minesweeper { + pub fn new() -> Self { + Default::default() + } + + //https://en.wikipedia.org/wiki/Xorshift + //from 0 to 15 + pub fn random(&mut self) -> usize { + let mut x = self.random_seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + self.random_seed = x; + 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) { + if y > 0 { + //above + if self.tiles[y - 1][x].mine == if_mine { + action(x, y - 1); + } + if x > 0 { + //above to the left + if self.tiles[y - 1][x - 1].mine == if_mine { + action(x - 1, y - 1); + } + } + if x < 15 { + //above to the right + if self.tiles[y - 1][x + 1].mine == if_mine { + action(x + 1, y - 1); + } + } + } + if x > 0 { + //to the left + if self.tiles[y][x - 1].mine == if_mine { + action(x - 1, y); + } + } + if x < 15 { + //to the right + if self.tiles[y][x + 1].mine == if_mine { + action(x + 1, y); + } + } + if y < 15 { + //below + if self.tiles[y + 1][x].mine == if_mine { + action(x, y + 1); + } + if x > 0 { + //below to the left + if self.tiles[y + 1][x - 1].mine == if_mine { + action(x - 1, y + 1); + } + } + if x < 15 { + //below to the right + if self.tiles[y + 1][x + 1].mine == if_mine { + action(x + 1, y + 1); + } + } + } + } + + pub fn new_tiles(&mut self) { + self.tiles = Default::default(); + //40 mines + for _ in 0..40 { + loop { + let x = self.random(); + let y = self.random(); + // + if !self.tiles[y][x].mine { + self.tiles[y][x].mine = true; + break; + } + } + } + //calculate touching + for y in 0..16 { + for x in 0..16 { + let mut touching = 0; + self.on_adjacent_tiles(x, y, |_, _| { + touching += 1; + }, true); + self.tiles[y][x].touching = touching; + } + } + } + // +} + diff --git a/src/window_likes/mod.rs b/src/window_likes/mod.rs new file mode 100644 index 0000000..31435b7 --- /dev/null +++ b/src/window_likes/mod.rs @@ -0,0 +1,9 @@ +pub mod desktop_background; +pub mod taskbar; +pub mod start_menu; +pub mod lock_screen; +pub mod workspace_indicator; + +pub mod minesweeper; +pub mod terminal; + diff --git a/src/window_likes/start_menu.rs b/src/window_likes/start_menu.rs new file mode 100644 index 0000000..17a82ed --- /dev/null +++ b/src/window_likes/start_menu.rs @@ -0,0 +1,191 @@ +use std::vec; +use std::vec::Vec; +use std::boxed::Box; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::highlight_button::HighlightButton; + +static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "System", "Misc", "Help", "Logout"]; + +#[derive(Clone)] +enum StartMenuMessage { + CategoryClick(&'static str), + WindowClick(&'static str), + Back, + ChangeAcknowledge, +} + +pub struct StartMenu { + dimensions: Dimensions, + components: Vec + Send>>, + current_focus: String, + old_focus: String, + y_each: usize, +} + +impl WindowLike for StartMenu { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.y_each = (self.dimensions[1] - 1) / CATEGORIES.len(); + self.add_category_components(); + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + //up and down + if key_press.key == 'k' || key_press.key == 'j' { + let old_focus_index = self.get_focus_index().unwrap(); + self.components[old_focus_index].handle_message(WindowMessage::Unfocus); + let current_focus_index = if key_press.key == 'j' { + if old_focus_index + 1 == self.components.len() { + 0 + } else { + old_focus_index + 1 + } + } else { + if old_focus_index == 0 { + self.components.len() - 1 + } else { + old_focus_index - 1 + } + }; + self.old_focus = self.current_focus.to_string(); + self.current_focus = self.components[current_focus_index].name().to_string(); + self.components[current_focus_index].handle_message(WindowMessage::Focus); + WindowMessageResponse::JustRerender + } else if key_press.key == '𐘂' { //the enter key + let focus_index = self.get_focus_index(); + if let Some(focus_index) = focus_index { + let r = self.components[focus_index].handle_message(WindowMessage::FocusClick); + self.handle_start_menu_message(r) + } else { + WindowMessageResponse::DoNothing + } + } else { + let current_focus_index = self.get_focus_index().unwrap(); + if let Some(n_index) = self.components[current_focus_index..].iter().position(|c| c.name().chars().next().unwrap_or('𐘂').to_lowercase().next().unwrap() == key_press.key) { + //now old focus, not current focus + self.components[current_focus_index].handle_message(WindowMessage::Unfocus); + self.old_focus = self.current_focus.clone(); + self.current_focus = self.components[current_focus_index + n_index].name().to_string(); + self.components[current_focus_index + n_index].handle_message(WindowMessage::Focus); + WindowMessageResponse::JustRerender + } else { + WindowMessageResponse::DoNothing + } + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //top thin border + DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top), + //right thin border + DrawInstructions::Rect([self.dimensions[0] - 1, 0], [1, self.dimensions[1]], theme_info.border_right_bottom), + //background + DrawInstructions::Rect([0, 1], [self.dimensions[0] - 1, self.dimensions[1] - 1], theme_info.background), + //mingde logo + DrawInstructions::Mingde([2, 2]), + //I truly don't know why, it should be - 44 but - 30 seems to work better :shrug: + DrawInstructions::Gradient([2, 42], [40, self.dimensions[1] - 30], [255, 201, 14], [225, 219, 77], 15), + ]; + for component in &self.components { + instructions.extend(component.draw(theme_info)); + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::StartMenu + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [175, 250] + } +} + +impl StartMenu { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + components: Vec::new(), + current_focus: String::new(), //placeholder, will be set in init + old_focus: String::new(), + y_each: 0, //will be set in add_category_components + } + } + + fn handle_start_menu_message(&mut self, message: Option) -> WindowMessageResponse { + if let Some(message) = message { + match message { + StartMenuMessage::CategoryClick(name) => { + if name == "Logout" { + WindowMessageResponse::Request(WindowManagerRequest::Lock) + } else { + self.current_focus = "Back".to_string(); + self.components = vec![ + Box::new(HighlightButton::new( + "Back".to_string(), [42, 1], [self.dimensions[0] - 42 - 1, self.y_each], "Back", StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true + )) + ]; + //add window buttons + let mut to_add: Vec<&str> = Vec::new(); + if name == "Games" { + to_add.push("Minesweeper"); + } else if name == "Files" { + to_add.push("Terminal"); + } + // + for a in 0..to_add.len() { + let w_name = to_add[a]; + self.components.push(Box::new(HighlightButton::new( + w_name.to_string(), [42, (a + 1) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], w_name, StartMenuMessage::WindowClick(w_name), StartMenuMessage::ChangeAcknowledge, false + ))); + } + WindowMessageResponse::JustRerender + } + }, + StartMenuMessage::WindowClick(name) => { + //open the selected window + WindowMessageResponse::Request(WindowManagerRequest::OpenWindow(name)) + }, + StartMenuMessage::Back => { + self.add_category_components(); + WindowMessageResponse::JustRerender + }, + StartMenuMessage::ChangeAcknowledge => { + // + WindowMessageResponse::JustRerender + }, + } + } else { + //maybe should be JustRerender? + WindowMessageResponse::DoNothing + } + } + + pub fn add_category_components(&mut self) { + self.current_focus = "About".to_string(); + self.components = Vec::new(); + for c in 0..CATEGORIES.len() { + let name = CATEGORIES[c]; + self.components.push(Box::new(HighlightButton::new( + name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name, StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0 + ))); + } + } + + pub fn get_focus_index(&self) -> Option { + self.components.iter().filter(|c| c.focusable()).position(|c| c.name() == &self.current_focus) + } +} + diff --git a/src/window_likes/taskbar.rs b/src/window_likes/taskbar.rs new file mode 100644 index 0000000..84da083 --- /dev/null +++ b/src/window_likes/taskbar.rs @@ -0,0 +1,128 @@ +use std::vec; +use std::vec::Vec; +use std::boxed::Box; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::toggle_button::{ ToggleButton, ToggleButtonAlignment }; +use crate::window_likes::start_menu::StartMenu; + +const PADDING: usize = 4; +const META_WIDTH: usize = 175; //of the window button + +#[derive(Clone)] +enum TaskbarMessage { + ShowStartMenu, + HideStartMenu, + Nothing, + // +} + +pub struct Taskbar { + dimensions: Dimensions, + components: Vec + Send>>, + windows_in_workspace: WindowsVec, + focused_id: usize, +} + +impl WindowLike for Taskbar { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.components = vec![ + Box::new(ToggleButton::new("start-button".to_string(), [PADDING, PADDING], [44, self.dimensions[1] - (PADDING * 2)], "Start", TaskbarMessage::ShowStartMenu, TaskbarMessage::HideStartMenu, false, Some(ToggleButtonAlignment::Left))), + ]; + WindowMessageResponse::JustRerender + }, + WindowMessage::Shortcut(shortcut) => { + match shortcut { + ShortcutType::StartMenu => { + let start_index = self.components.iter().position(|c| c.name() == "start-button").unwrap(); + let start_response = self.components[start_index].handle_message(WindowMessage::FocusClick); + self.handle_taskbar_message(start_response) + } + _ => WindowMessageResponse::DoNothing, + } + }, + WindowMessage::Info(info) => { + match info { + InfoType::WindowsInWorkspace(windows, focused_id) => { + self.windows_in_workspace = windows; + self.focused_id = focused_id; + WindowMessageResponse::JustRerender + } + _ => WindowMessageResponse::DoNothing, + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //top thin white border + DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top), + //the actual taskbar background + DrawInstructions::Rect([0, 1], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background), + ]; + for component in &self.components { + instructions.extend(component.draw(theme_info)); + } + for wi in 0..self.windows_in_workspace.len() { + //if too many windows to fit in taskbar... + if wi > (self.dimensions[0] - 200) / META_WIDTH { + // + break; + } + let info = &self.windows_in_workspace[wi]; + let mut b = ToggleButton::new(info.1.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], info.1, TaskbarMessage::Nothing, TaskbarMessage::Nothing, false, Some(ToggleButtonAlignment::Left)); + b.inverted = info.0 == self.focused_id; + instructions.extend(b.draw(theme_info)); + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Taskbar + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], TASKBAR_HEIGHT] + } +} + +impl Taskbar { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + components: Vec::new(), + windows_in_workspace: Vec::new(), + focused_id: 0, + } + } + + fn handle_taskbar_message(&mut self, message: Option) -> WindowMessageResponse { + if let Some(message) = message { + match message { + TaskbarMessage::ShowStartMenu => { + WindowMessageResponse::Request(WindowManagerRequest::OpenWindow("StartMenu")) + }, + TaskbarMessage::HideStartMenu => { + WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) + }, + _ => WindowMessageResponse::DoNothing, + } + } else { + //maybe should be JustRerender? + WindowMessageResponse::DoNothing + } + } +} + + diff --git a/src/window_likes/terminal.rs b/src/window_likes/terminal.rs new file mode 100644 index 0000000..ef3a689 --- /dev/null +++ b/src/window_likes/terminal.rs @@ -0,0 +1,116 @@ +use std::vec::Vec; +use std::vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const MONO_WIDTH: u8 = 8; +const LINE_HEIGHT: usize = 15; +const PADDING: usize = 4; + +#[derive(Default)] +pub struct Terminal { + dimensions: Dimensions, + lines: Vec, + actual_lines: Vec, //wrapping + actual_line_num: usize, //what line # is at the top, for scrolling + current_input: String, +} + +//for some reason key presses, then moving the window leaves the old window still there, behind it. weird + +impl WindowLike for Terminal { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.lines = vec!["Mingde Terminal".to_string(), "".to_string()]; + self.calc_actual_lines(); + WindowMessageResponse::JustRerender + }, + WindowMessage::KeyPress(key_press) => { + self.current_input += &key_press.key.to_string(); + self.calc_actual_lines(); + WindowMessageResponse::JustRerender + }, + WindowMessage::ChangeDimensions(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + // + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + 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.dimensions[1] - WINDOW_TOP_HEIGHT- PADDING * 2) / LINE_HEIGHT; + let mut text_y = WINDOW_TOP_HEIGHT + 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))); + text_y += LINE_HEIGHT; + } + instructions + } + + fn title(&self) -> &'static str { + "Terminal" + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [410, 410 + WINDOW_TOP_HEIGHT] + } + + fn resizable(&self) -> bool { + true + } +} + +impl Terminal { + pub fn new() -> Self { + Default::default() + } + + fn calc_actual_lines(&mut self) { + self.actual_lines = Vec::new(); + let max_chars_per_line = (self.dimensions[0] - PADDING * 2) / MONO_WIDTH as usize; + for line_num in 0..=self.lines.len() { + let mut working_line = if line_num == self.lines.len() { + "$ ".to_string() + &self.current_input + "█" + } else { + self.lines[line_num].clone() + }; + //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 { + push_string += &working_line_chars.next().unwrap().to_string(); + } + self.actual_lines.push(push_string); + working_line = working_line_chars.collect(); + } + } + } + } +} + diff --git a/src/window_likes/workspace_indicator.rs b/src/window_likes/workspace_indicator.rs new file mode 100644 index 0000000..53b7b4b --- /dev/null +++ b/src/window_likes/workspace_indicator.rs @@ -0,0 +1,74 @@ +use std::vec; +use std::vec::Vec; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, INDICATOR_HEIGHT }; +use crate::messages::{ WindowMessage, WindowMessageResponse, ShortcutType }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; + +const WIDTH: usize = 15; + +pub struct WorkspaceIndicator { + dimensions: Dimensions, + current_workspace: u8, +} + +impl WindowLike for WorkspaceIndicator { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + WindowMessageResponse::JustRerender + }, + WindowMessage::Shortcut(shortcut) => { + match shortcut { + ShortcutType::SwitchWorkspace(workspace) => { + self.current_workspace = workspace; + WindowMessageResponse::JustRerender + } + _ => WindowMessageResponse::DoNothing, + } + }, + _ => WindowMessageResponse::DoNothing, + } + } + + //simple + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![ + //background + DrawInstructions::Rect([0, 0], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background), + //bottom border + DrawInstructions::Rect([0, self.dimensions[1] - 1], [self.dimensions[0], 1], theme_info.border_right_bottom), + ]; + 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)); + } else { + instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], "times-new-roman", (w + 1).to_string(), theme_info.text, theme_info.background, None)); + } + } + instructions + } + + //properties + fn subtype(&self) -> WindowLikeType { + WindowLikeType::WorkspaceIndicator + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0], INDICATOR_HEIGHT] + } +} + +impl WorkspaceIndicator { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + current_workspace: 0, + } + } +} + + diff --git a/src/window_manager.rs b/src/window_manager.rs new file mode 100644 index 0000000..2532348 --- /dev/null +++ b/src/window_manager.rs @@ -0,0 +1,613 @@ +use std::vec::Vec; +use std::vec; +use std::collections::HashMap; +use std::fmt; +use std::boxed::Box; + +use linux_framebuffer::Framebuffer; +use std::sync::{ LazyLock, Mutex }; + +use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; +use crate::window_likes::desktop_background::DesktopBackground; +use crate::window_likes::taskbar::Taskbar; +use crate::window_likes::lock_screen::LockScreen; +use crate::window_likes::workspace_indicator::WorkspaceIndicator; +use crate::themes::{ ThemeInfo, Themes, get_theme_info }; +use crate::keyboard::{ KeyChar, uppercase_or_special }; +use crate::messages::*; + +use crate::window_likes::start_menu::StartMenu; +use crate::window_likes::minesweeper::Minesweeper; +use crate::window_likes::terminal::Terminal; + +pub const TASKBAR_HEIGHT: usize = 38; +pub const INDICATOR_HEIGHT: usize = 20; +pub const WINDOW_TOP_HEIGHT: usize = 26; + +static WRITER: LazyLock> = LazyLock::new(|| Mutex::new(Default::default())); + +//todo: close start menu if window focus next shortcut done + +pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { + let dimensions = [framebuffer_info.width, framebuffer_info.height]; + + let mut temp_vec = vec![0 as u8; framebuffer_info.height * framebuffer_info.stride * framebuffer_info.bytes_per_pixel]; + WRITER.lock().unwrap().init(framebuffer_info.clone(), temp_vec); + + let mut wm: WindowManager = WindowManager::new(framebuffer, dimensions); + + wm.render(None, false); + + // +} + +pub fn min(one: usize, two: usize) -> usize { + if one > two { two } else { one } +} + +/* +pub fn keyboard_emit(key_char: KeyChar) { + let mut kc = key_char; + if let KeyChar::Press(c) = kc { + if WM.lock().held_special_keys.contains(&"shift") { + kc = KeyChar::Press(uppercase_or_special(c)); + } + } + //unsafe { SERIAL1.lock().write_text(&format!("{:?}", &kc)); } + WM.lock().handle_message(WindowManagerMessage::KeyChar(kc)); +} +*/ + +#[derive(Debug)] +pub enum DrawInstructions { + Rect(Point, Dimensions, RGBColor), + Text(Point, &'static str, String, RGBColor, RGBColor, Option), //font and text + Gradient(Point, Dimensions, RGBColor, RGBColor, usize), + Mingde(Point), +} + +#[derive(Debug, PartialEq)] +pub enum WindowLikeType { + LockScreen, + Window, + DesktopBackground, + Taskbar, + StartMenu, + WorkspaceIndicator, +} + +pub trait WindowLike { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse; + + //properties + fn title(&self) -> &'static str { + "" + } + fn resizable(&self) -> bool { + false + } + fn subtype(&self) -> WindowLikeType; + fn draw(&self, theme_info: &ThemeInfo) -> Vec; + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions; //needs &self or its not object safe or some bullcrap +} + +#[derive(PartialEq)] +pub enum Workspace { + All, + Workspace(u8), //goes from 0-8 +} + +pub struct WindowLikeInfo { + id: usize, + window_like: Box, + top_left: Point, + dimensions: Dimensions, + workspace: Workspace, + fullscreen: bool, +} + +impl fmt::Debug for WindowLikeInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WindowLikeInfo").field("id", &self.id).field("top_left", &self.top_left).field("dimensions", &self.dimensions).field("window_like", &"todo: print this out too").finish() + } +} + +pub struct WindowManager { + id_count: usize, + window_infos: Vec, + dimensions: Dimensions, + theme: Themes, + focused_id: usize, + held_special_keys: Vec<&'static str>, + locked: bool, + current_workspace: u8, + framebuffer: Framebuffer, +} + +//1 is up, 2 is down + +impl WindowManager { + pub fn new(framebuffer: Framebuffer, dimensions: Dimensions) -> Self { + let mut wm = WindowManager { + id_count: 0, + window_infos: Vec::new(), + dimensions, + theme: Themes::Standard, + focused_id: 0, + held_special_keys: Vec::new(), + locked: false, + current_workspace: 0, + framebuffer, + }; + wm.lock(); + wm + } + + pub fn add_window_like(&mut self, mut window_like: Box, top_left: Point, dimensions: Option) { + let subtype = window_like.subtype(); + let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions)); + self.id_count = self.id_count + 1; + let id = self.id_count; + self.focused_id = id; + window_like.handle_message(WindowMessage::Init(dimensions)); + self.window_infos.push(WindowLikeInfo { + id, + window_like, + top_left, + dimensions, + workspace: if subtype == WindowLikeType::Window { + Workspace::Workspace(self.current_workspace) + } else { + Workspace::All + }, + fullscreen: false, + }); + } + + fn get_focused_index(&self) -> Option { + self.window_infos.iter().position(|w| w.id == self.focused_id) + } + + //should return an iterator but fuck it! + fn get_windows_in_workspace(&self, include_non_window: bool) -> Vec<&WindowLikeInfo> { + self.window_infos.iter().filter(|w| { + match w.workspace { + Workspace::Workspace(workspace) => workspace == self.current_workspace, + _ => include_non_window, //filter out taskbar, indicator, background, start menu, etc if true + } + }).collect() + } + + fn lock(&mut self) { + self.locked = true; + self.window_infos = Vec::new(); + self.add_window_like(Box::new(LockScreen::new()), [0, 0], None); + } + + fn unlock(&mut self) { + self.locked = false; + self.window_infos = Vec::new(); + self.add_window_like(Box::new(DesktopBackground::new()), [0, INDICATOR_HEIGHT], None); + self.add_window_like(Box::new(Taskbar::new()), [0, self.dimensions[1] - TASKBAR_HEIGHT], None); + self.add_window_like(Box::new(WorkspaceIndicator::new()), [0, 0], None); + } + + //if off_only is true, also handle request + 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(); + if (start_menu_exists && off_only) || !off_only { + 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; + if off_only { + self.handle_request(WindowManagerRequest::CloseStartMenu); + } + self.window_infos[taskbar_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::StartMenu)) + } else { + WindowMessageResponse::DoNothing + } + } + + fn taskbar_update_windows(&mut self) { + let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap(); + let mut relevant: WindowsVec = self.get_windows_in_workspace(false).iter().map(|w| (w.id, w.window_like.title())).collect(); + relevant.sort_by(|a, b| a.0.cmp(&b.0)); //sort by ids so order is consistent + let message = WindowMessage::Info(InfoType::WindowsInWorkspace( + relevant, + self.focused_id + )); + self.window_infos[taskbar_index].window_like.handle_message(message); + } + + fn move_index_to_top(&mut self, index: usize) { + let removed = self.window_infos.remove(index); + self.window_infos.push(removed); + } + + pub fn handle_message(&mut self, message: WindowManagerMessage) { + let mut use_saved_buffer = false; + let mut redraw_ids = None; + let response: WindowMessageResponse = match message { + WindowManagerMessage::KeyChar(key_char) => { + //check if is special key (key releases are guaranteed to be special keys) + //eg: ctrl, alt, command/windows, shift, or caps lock + match key_char { + KeyChar::Press(c) => { + let mut press_response = WindowMessageResponse::DoNothing; + if self.held_special_keys.contains(&"alt") && !self.locked { + //keyboard shortcut + let shortcuts = HashMap::from([ + ('s', ShortcutType::StartMenu), + (']', ShortcutType::FocusNextWindow), + ('q', ShortcutType::QuitWindow), + ('c', ShortcutType::CenterWindow), + ('f', ShortcutType::FullscreenWindow), + //move window a small amount + ('h', ShortcutType::MoveWindow(Direction::Left)), + ('j', ShortcutType::MoveWindow(Direction::Down)), + ('k', ShortcutType::MoveWindow(Direction::Up)), + ('l', ShortcutType::MoveWindow(Direction::Right)), + //move window to edges + ('H', ShortcutType::MoveWindowToEdge(Direction::Left)), + ('J', ShortcutType::MoveWindowToEdge(Direction::Down)), + ('K', ShortcutType::MoveWindowToEdge(Direction::Up)), + ('L', ShortcutType::MoveWindowToEdge(Direction::Right)), + // + //no 10th workspace + ('1', ShortcutType::SwitchWorkspace(0)), + ('2', ShortcutType::SwitchWorkspace(1)), + ('3', ShortcutType::SwitchWorkspace(2)), + ('4', ShortcutType::SwitchWorkspace(3)), + ('5', ShortcutType::SwitchWorkspace(4)), + ('6', ShortcutType::SwitchWorkspace(5)), + ('7', ShortcutType::SwitchWorkspace(6)), + ('8', ShortcutType::SwitchWorkspace(7)), + ('9', ShortcutType::SwitchWorkspace(8)), + //shfit + num key + ('!', ShortcutType::MoveWindowToWorkspace(0)), + ('@', ShortcutType::MoveWindowToWorkspace(1)), + ('#', ShortcutType::MoveWindowToWorkspace(2)), + ('$', ShortcutType::MoveWindowToWorkspace(3)), + ('%', ShortcutType::MoveWindowToWorkspace(4)), + ('^', ShortcutType::MoveWindowToWorkspace(5)), + ('&', ShortcutType::MoveWindowToWorkspace(6)), + ('*', ShortcutType::MoveWindowToWorkspace(7)), + ('(', ShortcutType::MoveWindowToWorkspace(8)), + // + ]); + if let Some(shortcut) = shortcuts.get(&c) { + match shortcut { + &ShortcutType::StartMenu => { + //send to taskbar + press_response = self.toggle_start_menu(false); + if press_response != WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) { + //only thing that needs to be rerendered is the start menu and taskbar + let start_menu_id = self.id_count + 1; + let taskbar_id = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap().id; + redraw_ids = Some(vec![start_menu_id, taskbar_id]); + } + }, + &ShortcutType::MoveWindow(direction) | &ShortcutType::MoveWindowToEdge(direction) => { + if let Some(focused_index) = self.get_focused_index() { + let focused_info = &self.window_infos[focused_index]; + if focused_info.window_like.subtype() == WindowLikeType::Window && !focused_info.fullscreen { + let delta = 15; + let window_x = self.window_infos[focused_index].top_left[0]; + let window_y = self.window_infos[focused_index].top_left[1]; + let mut changed = true; + if direction == Direction::Left { + if window_x == 0 { + changed = false; + } else if window_x < delta || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[0] = 0; + } else { + self.window_infos[focused_index].top_left[0] -= delta; + } + } else if direction == Direction::Down { + let max_y = self.dimensions[1] - TASKBAR_HEIGHT - focused_info.dimensions[1]; + if window_y == max_y { + changed = false; + } else if window_y > (max_y - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[1] = max_y; + } else { + self.window_infos[focused_index].top_left[1] += delta; + } + } else if direction == Direction::Up { + let min_y = INDICATOR_HEIGHT; + if window_y == min_y { + changed = false; + } else if window_y < (min_y + delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[1] = min_y; + } else { + self.window_infos[focused_index].top_left[1] -= delta; + } + } else if direction == Direction::Right { + let max_x = self.dimensions[0] - focused_info.dimensions[0]; + if window_x == max_x { + changed = false; + } else if window_x > (max_x - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) { + self.window_infos[focused_index].top_left[0] = max_x; + } else { + self.window_infos[focused_index].top_left[0] += delta; + } + } + if changed { + press_response = WindowMessageResponse::JustRerender; + //avoid drawing everything under the moving window, much more efficient + use_saved_buffer = true; + redraw_ids = Some(vec![self.focused_id]); + } + } + } + }, + &ShortcutType::SwitchWorkspace(workspace) => { + if self.current_workspace != workspace { + //close start menu if open + self.toggle_start_menu(true); + self.current_workspace = workspace; + //send to workspace indicator + let indicator_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::WorkspaceIndicator).unwrap(); + self.focused_id = self.window_infos[indicator_index].id; + self.window_infos[indicator_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::SwitchWorkspace(self.current_workspace))); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + }, + &ShortcutType::MoveWindowToWorkspace(workspace) => { + if self.current_workspace != workspace { + if let Some(focused_index) = self.get_focused_index() { + if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { + self.window_infos[focused_index].workspace = Workspace::Workspace(workspace); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + } + } + }, + &ShortcutType::FocusNextWindow => { + let current_index = self.get_focused_index().unwrap_or(0); + let mut new_focus_index = current_index; + loop { + new_focus_index += 1; + if new_focus_index == self.window_infos.len() { + new_focus_index = 0; + } + if self.window_infos[new_focus_index].window_like.subtype() == WindowLikeType::Window && self.window_infos[new_focus_index].workspace == Workspace::Workspace(self.current_workspace) { + //switch focus to this + self.focused_id = self.window_infos[new_focus_index].id; + //elevate it to the top + self.move_index_to_top(new_focus_index); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + break; + } else if new_focus_index == current_index { + break; //did a full loop, found no windows + } + } + }, + &ShortcutType::QuitWindow => { + if let Some(focused_index) = self.get_focused_index() { + if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { + self.window_infos.remove(focused_index); + self.taskbar_update_windows(); + press_response = WindowMessageResponse::JustRerender; + } + } + }, + &ShortcutType::CenterWindow => { + if let Some(focused_index) = self.get_focused_index() { + let window_dimensions = &self.window_infos[focused_index].dimensions; + self.window_infos[focused_index].top_left = [self.dimensions[0] / 2 - window_dimensions[0] / 2, self.dimensions[1] / 2 - window_dimensions[1] / 2]; + use_saved_buffer = true; + press_response = WindowMessageResponse::JustRerender; + } + }, + &ShortcutType::FullscreenWindow => { + if let Some(focused_index) = self.get_focused_index() { + let window_like = &self.window_infos[focused_index].window_like; + if window_like.subtype() == WindowLikeType::Window && window_like.resizable() { + //toggle fullscreen + self.window_infos[focused_index].fullscreen ^= true; + //todo: send message to window about resize + let new_dimensions; + if self.window_infos[focused_index].fullscreen { + new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]; + self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT]; + redraw_ids = Some(vec![self.window_infos[focused_index].id]); + } else { + new_dimensions = self.window_infos[focused_index].dimensions; + } + self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions(new_dimensions)); + press_response = WindowMessageResponse::JustRerender; + } + } + } + }; + } + } else { + //send to focused window + if let Some(focused_index) = self.get_focused_index() { + press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::KeyPress(KeyPress { + key: c, + held_special_keys: self.held_special_keys.clone(), + })); + //at most, only the focused window needs to be rerendered + redraw_ids = Some(vec![self.window_infos[focused_index].id]); + //requests can result in window openings and closings, etc + if press_response != WindowMessageResponse::JustRerender { + redraw_ids = None; + } + } + } + press_response + }, + KeyChar::SpecialPress(special_key) => { + //add to pressed keys + self.held_special_keys.push(special_key); + WindowMessageResponse::DoNothing + }, + KeyChar::SpecialRelease(special_key) => { + //remove it from pressed keys + let index = self.held_special_keys.iter().position(|sk| sk == &special_key).unwrap(); + self.held_special_keys.remove(index); + WindowMessageResponse::DoNothing + }, + } + }, + // + }; + if response != WindowMessageResponse::DoNothing { + match response { + WindowMessageResponse::Request(request) => self.handle_request(request), + _ => {}, + }; + self.render(redraw_ids, use_saved_buffer); + } + } + + pub fn handle_request(&mut self, request: WindowManagerRequest) { + let focused_index = self.get_focused_index().unwrap(); + let subtype = self.window_infos[focused_index].window_like.subtype(); + match request { + WindowManagerRequest::OpenWindow(w) => { + if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { + return; + } + let w: WindowBox = match w { + "Minesweeper" => Box::new(Minesweeper::new()), + "Terminal" => Box::new(Terminal::new()), + "StartMenu" => Box::new(StartMenu::new()), + _ => panic!("no such window"), + }; + //close start menu if open + self.toggle_start_menu(true); + let ideal_dimensions = w.ideal_dimensions(self.dimensions); + let top_left = match w.subtype() { + WindowLikeType::StartMenu => [0, self.dimensions[1] - TASKBAR_HEIGHT - ideal_dimensions[1]], + WindowLikeType::Window => [42, 42], + _ => [0, 0], + }; + self.add_window_like(w, top_left, Some(ideal_dimensions)); + self.taskbar_update_windows(); + }, + WindowManagerRequest::CloseStartMenu => { + if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { + return; + } + let start_menu_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::StartMenu); + if let Some(start_menu_index) = start_menu_index { + self.window_infos.remove(start_menu_index); + } + }, + WindowManagerRequest::Unlock => { + if subtype != WindowLikeType::LockScreen { + return; + } + self.unlock(); + }, + WindowManagerRequest::Lock => { + if subtype != WindowLikeType::StartMenu { + return; + } + self.lock(); + }, + }; + } + + //another issue with a huge vector of draw instructions; it takes up heap memory + pub fn render(&mut self, maybe_redraw_ids: Option>, use_saved_buffer: bool) { + let theme_info = get_theme_info(&self.theme).unwrap(); + //use in conjunction with redraw ids, so a window moving can work without redrawing everything, + //can just redraw the saved state + window + if use_saved_buffer { + WRITER.lock().unwrap().write_saved_buffer_to_raw(); + } + //get windows to redraw + let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new()); + let redraw_windows = self.get_windows_in_workspace(true); + let maybe_length = redraw_windows.len(); + let redraw_windows = redraw_windows.iter().filter(|w| { + //basically, maybe_redraw_ids was None + if redraw_ids.len() > 0 { + redraw_ids.contains(&w.id) + } else { + true + } + }); + //these are needed to decide when to snapshot + let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1; + let mut w_index = 0; + for window_info in redraw_windows { + //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", &window_info.window_like.subtype())); } + let window_dimensions = if window_info.fullscreen { + [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT] + } else { + window_info.dimensions + }; + let mut instructions = Vec::new(); + if window_info.window_like.subtype() == WindowLikeType::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(); + } + //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 { + //draw window top decorations and what not + instructions.extend(vec![ + //left top border + 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), + //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), + //top bottom border + DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top), + //right bottom border + DrawInstructions::Rect([window_dimensions[0] - 1, 1], [1, window_dimensions[1] - 1], theme_info.border_right_bottom), + DrawInstructions::Rect([1, window_dimensions[1] - 1], [window_dimensions[0] - 1, 1], theme_info.border_right_bottom), + ]); + } + let mut framebuffer_info = WRITER.lock().unwrap().get_info(); + let bytes_per_pixel = framebuffer_info.bytes_per_pixel; + let window_width = window_dimensions[0]; + let window_height = window_dimensions[1]; + framebuffer_info.width = window_width; + framebuffer_info.height = window_height; + framebuffer_info.stride = window_width; + //make a writer just for the window + let mut window_writer: FramebufferWriter = Default::default(); + let mut temp_vec = vec![0 as u8; window_width * window_height * bytes_per_pixel]; + window_writer.init(framebuffer_info, temp_vec); + for instruction in instructions { + //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", instruction)); } + match instruction { + DrawInstructions::Rect(top_left, dimensions, color) => { + //try and prevent overflows out of the window + let true_dimensions = [ + min(dimensions[0], window_dimensions[0] - top_left[0]), + min(dimensions[1], window_dimensions[1] - top_left[1]), + ]; + 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::Mingde(top_left) => { + window_writer._draw_mingde(top_left); + }, + DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => { + window_writer.draw_gradient(top_left, dimensions, start_color, end_color, steps); + }, + } + } + WRITER.lock().unwrap().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer()); + w_index += 1; + //core::mem::drop(temp_vec); + } + self.framebuffer.write_frame(WRITER.lock().unwrap().get_buffer()); + } +} +