Files
ming-wm/src/window_manager.rs
2025-02-08 07:13:24 +00:00

834 lines
37 KiB
Rust

use std::vec::Vec;
use std::vec;
use std::collections::{ HashMap, VecDeque };
use std::fmt;
use std::boxed::Box;
use std::io::{ stdin, stdout, BufReader, BufRead, Write };
use std::process::exit;
use std::cell::RefCell;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use std::env;
use std::process::{ Command, Stdio };
use linux_framebuffer::Framebuffer;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::cursor;
use serde::{ Deserialize, Serialize };
use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor };
use crate::themes::{ ThemeInfo, Themes, get_theme_info };
use crate::utils::{ min, key_to_char, point_inside };
use crate::messages::*;
use crate::proxy_window_like::ProxyWindowLike;
use crate::essential::desktop_background::DesktopBackground;
use crate::essential::taskbar::Taskbar;
use crate::essential::lock_screen::LockScreen;
use crate::essential::workspace_indicator::WorkspaceIndicator;
use crate::essential::start_menu::StartMenu;
use crate::essential::about::About;
use crate::essential::help::Help;
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
//use crate::logging::log;
//todo: a lot of the usize should be changed to u16
pub const TASKBAR_HEIGHT: usize = 38;
pub const INDICATOR_HEIGHT: usize = 20;
const WINDOW_TOP_HEIGHT: usize = 26;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum KeyChar {
Press(char),
Alt(char),
Ctrl(char),
}
enum ThreadMessage {
KeyChar(KeyChar),
Touch(usize, usize),
Exit,
}
pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
let args: Vec<_> = env::args().collect();
let rotate = args.contains(&"rotate".to_string());
let framebuffer_info = if rotate {
FramebufferInfo {
byte_len: framebuffer_info.byte_len,
width: framebuffer_info.height,
height: framebuffer_info.width,
bytes_per_pixel: framebuffer_info.bytes_per_pixel,
stride: framebuffer_info.height,
old_stride: Some(framebuffer_info.stride),
}
} else {
framebuffer_info
};
let dimensions = [framebuffer_info.width, framebuffer_info.height];
println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
let mut writer: FramebufferWriter = Default::default();
writer.init(framebuffer_info.clone());
let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate);
wm.draw(None, false);
let mut stdout = stdout().into_raw_mode().unwrap();
write!(stdout, "{}", cursor::Hide).unwrap();
stdout.flush().unwrap();
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
//read key presses
thread::spawn(move || {
let stdin = stdin().lock();
for c in stdin.keys() {
if let Some(kc) = key_to_char(c.unwrap()) {
//do not allow exit when locked unless debugging
//if kc == KeyChar::Alt('E') {
if kc == KeyChar::Alt('E') {
tx.send(ThreadMessage::Exit).unwrap();
} else {
tx.send(ThreadMessage::KeyChar(kc)).unwrap();
}
}
thread::sleep(Duration::from_millis(1));
}
});
let touch = args.contains(&"touch".to_string());
//read touchscreen presses (hopefully)
thread::spawn(move || {
//spawn evtest, parse it for touch coords
if touch {
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
let mut x: Option<usize> = None;
let mut y: Option<usize> = None;
for line in reader.lines() {
let line = line.unwrap();
if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") {
let value: Vec<_> = line.split("), value ").collect();
let value = value[value.len() - 1].parse::<usize>().unwrap();
if line.contains(&"ABS_X") {
x = Some(value);
} else {
y = Some(value);
}
if x.is_some() && y.is_some() {
tx1.send(ThreadMessage::Touch(x.unwrap(), y.unwrap())).unwrap();
x = None;
y = None;
}
}
thread::sleep(Duration::from_millis(1));
}
}
});
if touch {
//opens osk
wm.handle_message(WindowManagerMessage::Touch(1, 1));
}
for message in rx {
match message {
ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())),
ThreadMessage::Touch(x, y) => wm.handle_message(WindowManagerMessage::Touch(x, y)),
ThreadMessage::Exit => {
if !wm.locked {
write!(stdout, "{}", cursor::Show).unwrap();
stdout.suspend_raw_mode().unwrap();
exit(0);
}
},
};
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum DrawInstructions {
Rect(Point, Dimensions, RGBColor),
Text(Point, Vec<String>, String, RGBColor, RGBColor, Option<usize>, Option<u8>), //font and text
Gradient(Point, Dimensions, RGBColor, RGBColor, usize),
Bmp(Point, String, bool),
Circle(Point, usize, RGBColor),
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum WindowLikeType {
LockScreen,
Window,
DesktopBackground,
Taskbar,
StartMenu,
WorkspaceIndicator,
OnscreenKeyboard,
}
pub trait WindowLike {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse;
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions>;
//properties
fn title(&self) -> String {
String::new()
}
fn resizable(&self) -> bool {
false
}
fn subtype(&self) -> WindowLikeType;
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: WindowBox,
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 {
writer: RefCell<FramebufferWriter>,
rotate: bool,
id_count: usize,
window_infos: Vec<WindowLikeInfo>,
osk: Option<WindowLikeInfo>,
dimensions: Dimensions,
theme: Themes,
focused_id: usize,
pub locked: bool,
current_workspace: u8,
framebuffer: Framebuffer,
clipboard: Option<String>,
}
//1 is up, 2 is down
impl WindowManager {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool) -> Self {
let mut wm = WindowManager {
writer: RefCell::new(writer),
rotate,
id_count: 0,
window_infos: Vec::new(),
osk: None,
dimensions,
theme: Themes::Standard,
focused_id: 0,
locked: false,
current_workspace: 0,
framebuffer,
clipboard: None,
};
wm.lock();
wm
}
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike>, 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;
window_like.handle_message(WindowMessage::Init(dimensions));
let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
let window_info = WindowLikeInfo {
id,
window_like,
top_left,
dimensions,
workspace: if subtype == WindowLikeType::Window {
Workspace::Workspace(self.current_workspace)
} else {
Workspace::All
},
fullscreen: false,
};
if subtype == WindowLikeType::OnscreenKeyboard {
self.osk = Some(window_info);
} else {
self.focused_id = id;
self.window_infos.push(window_info);
}
}
fn get_focused_index(&self) -> Option<usize> {
self.window_infos.iter().position(|w| w.id == self.focused_id)
}
//used to return Vec<&WindowLikeInfo>, doesn't anymore for good reason
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
//written confusingly but it works I promise
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().to_string())).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::Alt(c) => {
let mut press_response = WindowMessageResponse::DoNothing;
if !self.locked {
//keyboard shortcut
let shortcuts = HashMap::from([
//alt+e is terminate program (ctrl+c)
('s', ShortcutType::StartMenu),
('[', ShortcutType::FocusPrevWindow),
(']', ShortcutType::FocusNextWindow),
('q', ShortcutType::QuitWindow),
('c', ShortcutType::CenterWindow),
('f', ShortcutType::FullscreenWindow),
('w', ShortcutType::HalfWidthWindow),
('C', ShortcutType::ClipboardCopy),
('P', ShortcutType::ClipboardPaste(String::new())),
//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 redrawed 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::JustRedraw;
//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 desktop background
let desktop_background_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::DesktopBackground).unwrap();
self.window_infos[desktop_background_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::SwitchWorkspace(self.current_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::JustRedraw;
}
},
&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::JustRedraw;
}
}
}
},
&ShortcutType::FocusPrevWindow | &ShortcutType::FocusNextWindow => {
let current_index = self.get_focused_index().unwrap_or(0);
let mut new_focus_index = current_index;
loop {
if shortcut == &ShortcutType::FocusPrevWindow {
if new_focus_index == 0 {
new_focus_index = self.window_infos.len() - 1;
} else {
new_focus_index -= 1;
}
} else {
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::JustRedraw;
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::JustRedraw;
}
}
},
&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::JustRedraw;
}
},
&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[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
press_response = WindowMessageResponse::JustRedraw;
}
}
},
&ShortcutType::HalfWidthWindow => {
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() {
self.window_infos[focused_index].fullscreen = false;
//full height, half width
self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT];
let new_dimensions = [self.dimensions[0] / 2, self.dimensions[1] - INDICATOR_HEIGHT - TASKBAR_HEIGHT];
self.window_infos[focused_index].dimensions = new_dimensions;
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT]));
press_response = WindowMessageResponse::JustRedraw;
}
}
},
&ShortcutType::ClipboardCopy => {
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 {
press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::ClipboardCopy));
}
}
},
&ShortcutType::ClipboardPaste(_) => {
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 && self.clipboard.is_some() {
press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::ClipboardPaste(self.clipboard.clone().unwrap())));
}
}
},
};
}
}
press_response
},
KeyChar::Press(c) | KeyChar::Ctrl(c) => {
let mut press_response = WindowMessageResponse::DoNothing;
//send to focused window
if let Some(focused_index) = self.get_focused_index() {
press_response = self.window_infos[focused_index].window_like.handle_message(if key_char == KeyChar::Press(c) {
WindowMessage::KeyPress(KeyPress {
key: c,
})
} else {
WindowMessage::CtrlKeyPress(KeyPress {
key: c,
})
});
//at most, only the focused window needs to be redrawed
redraw_ids = Some(vec![self.window_infos[focused_index].id]);
//requests can result in window openings and closings, etc
if press_response != WindowMessageResponse::JustRedraw {
redraw_ids = None;
}
}
press_response
},
}
},
WindowManagerMessage::Touch(x, y) => {
println!("{}, {}", x, y);
if x < 100 && y < 100 {
//toggle onscreen keyboard if top left keyboard clicked
if self.osk.is_some() {
self.osk = None;
} else {
let osk = Box::new(OnscreenKeyboard::new());
let ideal_dimensions = osk.ideal_dimensions(self.dimensions);
self.add_window_like(osk, [175, self.dimensions[1] - TASKBAR_HEIGHT - 250], Some(ideal_dimensions));
}
WindowMessageResponse::JustRedraw
} else {
//see if in onscreen keyboard, if so send to it after offsetting coords
if self.osk.is_some() {
let osk = self.osk.as_mut().unwrap();
if point_inside([x, y], osk.top_left, osk.dimensions) {
osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1]))
} else {
WindowMessageResponse::DoNothing
}
} else {
WindowMessageResponse::DoNothing
}
}
}
};
if response != WindowMessageResponse::DoNothing {
match response {
WindowMessageResponse::Request(request) => self.handle_request(request),
_ => {},
};
self.draw(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: Option<WindowBox> = match w.as_str() {
"Minesweeper" => Some(Box::new(ProxyWindowLike::new_rust("minesweeper"))),
"Reversi" => Some(Box::new(ProxyWindowLike::new_rust("reversi"))),
"Malvim" => Some(Box::new(ProxyWindowLike::new_rust("malvim"))),
"Terminal" => Some(Box::new(ProxyWindowLike::new_rust("terminal"))),
"Audio Player" => Some(Box::new(ProxyWindowLike::new_rust("audio_player"))),
"File Explorer" => Some(Box::new(ProxyWindowLike::new_rust("file_explorer"))),
"StartMenu" => Some(Box::new(StartMenu::new())),
"About" => Some(Box::new(About::new())),
"Help" => Some(Box::new(Help::new())),
_ => None,
};
if w.is_none() {
return;
}
let w = w.unwrap();
//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();
},
WindowManagerRequest::ClipboardCopy(content) => {
self.clipboard = Some(content);
},
WindowManagerRequest::DoKeyChar(kc) => {
self.handle_message(WindowManagerMessage::KeyChar(kc));
},
};
}
fn get_true_top_left(top_left: &Point, is_window: bool) -> Point {
[top_left[0], top_left[1] + if is_window { WINDOW_TOP_HEIGHT } else { 0 }]
}
//another issue with a huge vector of draw instructions; it takes up heap memory
pub fn draw(&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 {
self.writer.borrow_mut().write_saved_buffer_to_raw();
}
//get windows to redraw
let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new());
let mut all_in_workspace = self.get_windows_in_workspace(true);
if let Some(osk) = &self.osk {
all_in_workspace.push(osk);
}
let maybe_length = all_in_workspace.len();
let redraw_windows = all_in_workspace.iter().filter(|w| {
//basically, maybe_redraw_ids was None
if redraw_ids.len() > 0 {
redraw_ids.contains(&w.id) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard
} 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 {
let window_dimensions = if window_info.fullscreen {
[self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
} else {
window_info.dimensions
};
let mut instructions = VecDeque::from(window_info.window_like.draw(&theme_info));
let is_window = window_info.window_like.subtype() == WindowLikeType::Window;
if is_window {
//if this is the top most window to draw, snapshot
if w_index == max_index && !use_saved_buffer && redraw_ids.len() == 0 {
self.writer.borrow_mut().save_buffer();
}
//offset top left by the window top height for windows (because windows can't draw in that region)
instructions = instructions.iter().map(|instruction| {
match instruction {
DrawInstructions::Rect(top_left, dimensions, color) => DrawInstructions::Rect(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *color),
DrawInstructions::Circle(centre, radius, color) => DrawInstructions::Circle(WindowManager::get_true_top_left(centre, is_window), *radius, *color),
DrawInstructions::Text(top_left, fonts, text, color, bg_color, horiz_spacing, mono_width) => DrawInstructions::Text(WindowManager::get_true_top_left(top_left, is_window), fonts.clone(), text.clone(), *color, *bg_color, *horiz_spacing, *mono_width),
DrawInstructions::Bmp(top_left, path, reverse) => DrawInstructions::Bmp(WindowManager::get_true_top_left(top_left, is_window), path.to_string(), *reverse),
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => DrawInstructions::Gradient(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *start_color, *end_color, *steps),
}
}).collect();
//draw window background
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
//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], vec!["times-new-roman".to_string()], window_info.window_like.title().to_string(), theme_info.top_text, theme_info.top, None, None),
//top bottom border
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
//right bottom border
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 = self.writer.borrow().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;
framebuffer_info.byte_len = window_width * window_height * bytes_per_pixel;
//make a writer just for the window
let mut window_writer: FramebufferWriter = Default::default();
window_writer.init(framebuffer_info);
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::Circle(centre, radius, color) => {
window_writer.draw_circle(centre, radius, color);
},
DrawInstructions::Text(top_left, fonts, text, color, bg_color, horiz_spacing, mono_width) => {
window_writer.draw_text(top_left, fonts, &text, color, bg_color, horiz_spacing.unwrap_or(1), mono_width);
},
DrawInstructions::Bmp(top_left, path, reverse) => {
window_writer.draw_bmp(top_left, path, reverse);
},
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => {
window_writer.draw_gradient(top_left, dimensions, start_color, end_color, steps);
},
}
}
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer());
w_index += 1;
}
//could probably figure out a way to do borrow() when self.rotate is false but does it matter?
let mut writer_borrow = self.writer.borrow_mut();
let frame = if self.rotate { writer_borrow.get_transposed_buffer() } else { writer_borrow.get_buffer() };
self.framebuffer.write_frame(frame);
}
}