ported ming-os graphics to linux framebuffer
This commit is contained in:
78
src/components/highlight_button.rs
Normal file
78
src/components/highlight_button.rs
Normal 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
21
src/components/mod.rs
Normal 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
|
||||
}
|
||||
|
||||
90
src/components/toggle_button.rs
Normal file
90
src/components/toggle_button.rs
Normal 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
228
src/framebuffer.rs
Normal 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
52
src/fs.rs
Normal 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
109
src/keyboard.rs
Normal 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
37
src/main.rs
Normal 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
94
src/messages.rs
Normal 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
44
src/themes.rs
Normal 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;
|
||||
}
|
||||
|
||||
44
src/window_likes/desktop_background.rs
Normal file
44
src/window_likes/desktop_background.rs
Normal 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] }
|
||||
}
|
||||
}
|
||||
|
||||
79
src/window_likes/lock_screen.rs
Normal file
79
src/window_likes/lock_screen.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
350
src/window_likes/minesweeper.rs
Normal file
350
src/window_likes/minesweeper.rs
Normal 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
9
src/window_likes/mod.rs
Normal 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;
|
||||
|
||||
191
src/window_likes/start_menu.rs
Normal file
191
src/window_likes/start_menu.rs
Normal 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
128
src/window_likes/taskbar.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
116
src/window_likes/terminal.rs
Normal file
116
src/window_likes/terminal.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
src/window_likes/workspace_indicator.rs
Normal file
74
src/window_likes/workspace_indicator.rs
Normal 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
613
src/window_manager.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user