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 = None; let mut y: Option = 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::().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, RGBColor, RGBColor, Option, Option), //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; //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, rotate: bool, id_count: usize, window_infos: Vec, osk: Option, dimensions: Dimensions, theme: Themes, focused_id: usize, pub locked: bool, current_workspace: u8, framebuffer: Framebuffer, clipboard: Option, } //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, top_left: Point, dimensions: Option) { let subtype = window_like.subtype(); let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions)); self.id_count = self.id_count + 1; let id = self.id_count; 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 { 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 = 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>, 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); } }