ported ming-os graphics to linux framebuffer

This commit is contained in:
stjet
2024-10-12 06:18:05 +00:00
commit f595d4f43c
97 changed files with 2370 additions and 0 deletions

View File

@@ -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<T> {
name_: String,
top_left: Point,
size: Dimensions,
text: &'static str,
pub highlighted: bool,
click_return: T,
toggle_highlight_return: T, //also unhighlight return
}
impl<T: Clone> Component<T> for HighlightButton<T> {
fn handle_message(&mut self, message: WindowMessage) -> Option<T> {
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<DrawInstructions> {
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<T> HighlightButton<T> {
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,
}
}
}

21
src/components/mod.rs Normal file
View File

@@ -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<T> {
fn handle_message(&mut self, message: WindowMessage) -> Option<T>;
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions>;
//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
}

View File

@@ -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<T> {
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<T: Clone> Component<T> for ToggleButton<T> {
fn handle_message(&mut self, message: WindowMessage) -> Option<T> {
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<DrawInstructions> {
//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<T> ToggleButton<T> {
pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, unclick_return: T, draw_bg: bool, alignment: Option<ToggleButtonAlignment>) -> Self {
Self {
name_,
top_left,
size,
text,
click_return,
unclick_return,
draw_bg,
inverted: false,
alignment: alignment.unwrap_or(ToggleButtonAlignment::Centre),
}
}
}

228
src/framebuffer.rs Normal file
View File

@@ -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<Vec<u8>>, 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<u8>,
saved_buffer: Option<Vec<u8>>,
}
impl FramebufferWriter {
pub fn init(&mut self, info: FramebufferInfo, buffer: Vec<u8>) {
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<u8>) {
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,
}
}
}

52
src/fs.rs Normal file
View File

@@ -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<Vec<u8>>, u8)> {
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();
if path_chars[0] == c {
let mut ch: Vec<Vec<u8>> = 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<u8> should be [u8; 3] but thats a job for another day
pub fn get_bmp(path: &str) -> Vec<Vec<Vec<u8>>> {
let mut bmp: Vec<Vec<Vec<u8>>> = 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
}

109
src/keyboard.rs Normal file
View File

@@ -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<KeyChar> {
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
}
}

37
src/main.rs Normal file
View File

@@ -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);
//
}

94
src/messages.rs Normal file
View File

@@ -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<dyn WindowLike + Send>;
/*
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<title, name)>, focused id
//
}
pub enum WindowMessage {
Init(Dimensions),
KeyPress(KeyPress),
Shortcut(ShortcutType),
Info(InfoType),
Focus,
Unfocus,
FocusClick,
ChangeDimensions(Dimensions),
//
}

44
src/themes.rs Normal file
View File

@@ -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<ThemeInfo> {
for pair in THEME_INFOS {
if &pair.0 == theme {
return Some(pair.1);
}
}
return None;
}

View File

@@ -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<DrawInstructions> {
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] }
}
}

View File

@@ -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<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),
]
}
//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(),
}
}
}

View File

@@ -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<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),
]
} 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;
}
}
}
//
}

9
src/window_likes/mod.rs Normal file
View File

@@ -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;

View File

@@ -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<Box<dyn Component<StartMenuMessage> + 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<DrawInstructions> {
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<StartMenuMessage>) -> 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<usize> {
self.components.iter().filter(|c| c.focusable()).position(|c| c.name() == &self.current_focus)
}
}

128
src/window_likes/taskbar.rs Normal file
View File

@@ -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<Box<dyn Component<TaskbarMessage> + 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<DrawInstructions> {
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<TaskbarMessage>) -> 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
}
}
}

View File

@@ -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<String>,
actual_lines: Vec<String>, //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<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.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();
}
}
}
}
}

View File

@@ -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<DrawInstructions> {
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,
}
}
}

613
src/window_manager.rs Normal file
View File

@@ -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<Mutex<FramebufferWriter>> = 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<u8>), //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<DrawInstructions>;
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<dyn WindowLike + Send>,
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<WindowLikeInfo>,
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<dyn WindowLike + Send>, top_left: Point, dimensions: Option<Dimensions>) {
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<usize> {
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<Vec<usize>>, 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());
}
}