ported ming-os graphics to linux framebuffer
This commit is contained in:
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user