diff --git a/Cargo.lock b/Cargo.lock index 52d2612cda..f90960b990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2634,6 +2634,32 @@ dependencies = [ name = "port_io" version = "0.2.1" +[[package]] +name = "porthole" +version = "0.1.0" +dependencies = [ + "core2", + "device_manager", + "event_types", + "font", + "hpet", + "keycodes_ascii", + "log", + "memory", + "memory_structs", + "mouse", + "mouse_data", + "mpmc", + "multicore_bringup", + "page_attribute_table", + "scheduler", + "spawn", + "spin 0.9.4", + "stdio", + "task", + "zerocopy", +] + [[package]] name = "ppv-lite86" version = "0.2.17" diff --git a/applications/porthole/Cargo.toml b/applications/porthole/Cargo.toml new file mode 100644 index 0000000000..e5c0c07e77 --- /dev/null +++ b/applications/porthole/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "porthole" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mpmc = "0.1.6" +spin = "0.9.0" +zerocopy = "0.5.0" +core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } + +[dependencies.log] +version = "0.4.8" + +[dependencies.memory] +path = "../../kernel/memory" + +[dependencies.memory_structs] +path = "../../kernel/memory_structs" + +[target.'cfg(target_arch = "x86_64")'.dependencies] +page_attribute_table = { path = "../../kernel/page_attribute_table" } + +[dependencies.multicore_bringup] +path = "../../kernel/multicore_bringup" + +[dependencies.spawn] +path = "../../kernel/spawn" + +[dependencies.task] +path = "../../kernel/task" + +[dependencies.scheduler] +path = "../../kernel/scheduler" + +[dependencies.stdio] +path = "../../libs/stdio" + +[dependencies.hpet] +path = "../../kernel/acpi/hpet" + +[dependencies.mouse_data] +path = "../../libs/mouse_data" + +[dependencies.mouse] +path = "../../kernel/mouse" + +[dependencies.event_types] +path = "../../kernel/event_types" + +[dependencies.keycodes_ascii] +path = "../../libs/keycodes_ascii" + +[dependencies.font] +path = "../../kernel/font" + +[dependencies.device_manager] +path = "../../kernel/device_manager" + +[lib] +crate-type = ["rlib"] \ No newline at end of file diff --git a/applications/porthole/src/lib.rs b/applications/porthole/src/lib.rs new file mode 100644 index 0000000000..2614531348 --- /dev/null +++ b/applications/porthole/src/lib.rs @@ -0,0 +1,1308 @@ +#![no_std] +#![feature(slice_ptr_get)] +#![feature(slice_flatten)] +extern crate alloc; +extern crate device_manager; +extern crate hpet; +extern crate memory; +extern crate mouse; +extern crate mouse_data; +extern crate multicore_bringup; +extern crate scheduler; +extern crate spin; +extern crate task; +use alloc::format; +use alloc::sync::Arc; +use core::marker::PhantomData; +use core::ops::{Add, Sub}; +use core::slice::IterMut; +use log::{debug, info}; +use spin::{Mutex, MutexGuard, Once, RwLockReadGuard}; + +use event_types::Event; +use mpmc::Queue; + +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH, FONT_BASIC}; +use hpet::{get_hpet, Hpet}; +use memory::{ + BorrowedMappedPages, BorrowedSliceMappedPages, Mutable, PhysicalAddress, PteFlags, PteFlagsArch, +}; +use mouse_data::MouseEvent; +pub static WINDOW_MANAGER: Once> = Once::new(); +static TITLE_BAR_HEIGHT: usize = 20; +static SCREEN_WIDTH: usize = 1024; +static SCREEN_HEIGHT: usize = 768; + +/// Controls amount of visible `Window` we see when we move a `Window` out of the screen +static WINDOW_VISIBLE_GAP: i32 = 20; + +/// Controls amount of visible mouse we see when we move the mouse out of the screen +static MOUSE_VISIBLE_GAP: i32 = 3; + +type Color = u32; +static DEFAULT_BORDER_COLOR: Color = 0x141414; +static DEFAULT_TEXT_COLOR: Color = 0xFBF1C7; +static DEFAULT_WINDOW_COLOR: Color = 0x3C3836; + +static MOUSE_POINTER_IMAGE: [[u32; 18]; 11] = { + const T: u32 = 0xFF0000; + const C: u32 = 0x000000; // Cursor + const B: u32 = 0xFFFFFF; // Border + [ + [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, T, T], + [T, B, C, C, C, C, C, C, C, C, C, C, C, C, B, T, T, T], + [T, T, B, C, C, C, C, C, C, C, C, C, C, B, T, T, T, T], + [T, T, T, B, C, C, C, C, C, C, C, C, B, T, T, T, T, T], + [T, T, T, T, B, C, C, C, C, C, C, C, C, B, B, T, T, T], + [T, T, T, T, T, B, C, C, C, C, C, C, C, C, C, B, B, T], + [T, T, T, T, T, T, B, C, C, C, C, B, B, C, C, C, C, B], + [T, T, T, T, T, T, T, B, C, C, B, T, T, B, B, C, B, T], + [T, T, T, T, T, T, T, T, B, C, B, T, T, T, T, B, B, T], + [T, T, T, T, T, T, T, T, T, B, B, T, T, T, T, T, T, T], + [T, T, T, T, T, T, T, T, T, T, B, T, T, T, T, T, T, T], + ] +}; + +// Useful toy application that shows the real time performance +pub struct FpsCounter { + window: Arc>, + hpet: RwLockReadGuard<'static, BorrowedMappedPages>, + time_it_took_to_render: u64, + timer_freq: u64, + total_frames: u64, + total_time: u64, + avg_fps: u64, + avg_time: u64, + avg_fps_str: String, + avg_time_str: String, +} + +impl FpsCounter { + pub fn new(window: Arc>) -> Result { + let hpet = get_hpet().ok_or("Unable to get hpet")?; + let time_it_took_to_render = hpet.get_counter(); + let timer_freq = hpet.counter_period_femtoseconds() as u64; + Ok(Self { + window, + hpet, + time_it_took_to_render, + timer_freq, + total_frames: 0, + total_time: 0, + avg_fps: 0, + avg_time: 0, + avg_fps_str: format!("Frames per second:"), + avg_time_str: format!("Median frame time in micro seconds:"), + }) + } + + fn calculate_next_frame_time(&mut self) { + let time = self.hpet.get_counter(); + let diff = (time - self.time_it_took_to_render) * self.timer_freq / 1_000_000_000; + self.total_time += diff; + self.time_it_took_to_render = time; + self.total_frames += 1; + } + + fn reset_counters(&mut self) { + // this equals to a second + if self.total_time >= 1_000_000 { + self.avg_fps = self.total_frames; + self.avg_time = self.total_time / self.total_frames; + self.total_time = 0; + self.total_frames = 0; + } + } + + pub fn run(&mut self) -> Result<(), &'static str> { + self.calculate_next_frame_time(); + self.reset_counters(); + self.draw()?; + Ok(()) + } + + fn draw(&mut self) -> Result<(), &'static str> { + self.window.lock().draw_rectangle(DEFAULT_WINDOW_COLOR)?; + self.window + .lock() + .display_window_title(DEFAULT_TEXT_COLOR, DEFAULT_BORDER_COLOR)?; + self.print_avg_fps()?; + self.print_avg_time()?; + Ok(()) + } + + fn print_avg_fps(&mut self) -> Result<(), &'static str> { + let mut drawable_area = self.window.lock().drawable_area().to_relative_pos(); + let avg_fps = self.avg_fps.to_string(); + print_string( + &mut self.window.lock(), + &drawable_area, + &self.avg_fps_str, + 0xF8FF0E, + DEFAULT_WINDOW_COLOR, + )?; + drawable_area.x = (CHARACTER_WIDTH * self.avg_fps_str.len()) as u32; + print_string( + &mut self.window.lock(), + &drawable_area, + &avg_fps, + 0x20F065, + DEFAULT_WINDOW_COLOR, + )?; + Ok(()) + } + + fn print_avg_time(&mut self) -> Result<(), &'static str> { + let mut drawable_area = self.window.lock().drawable_area().to_relative_pos(); + drawable_area.y += (CHARACTER_HEIGHT + 1) as u32; + let avg_time = self.avg_time.to_string(); + print_string( + &mut self.window.lock(), + &drawable_area, + &self.avg_time_str, + 0xF8FF0E, + DEFAULT_WINDOW_COLOR, + )?; + drawable_area.x = (CHARACTER_WIDTH * self.avg_time_str.len()) as u32; + print_string( + &mut self.window.lock(), + &drawable_area, + &avg_time, + 0x20F065, + DEFAULT_WINDOW_COLOR, + )?; + Ok(()) + } +} + +pub struct App { + window: Arc>, + text: TextDisplayInfo, +} + +impl App { + pub fn new(window: Arc>, text: TextDisplayInfo) -> Self { + Self { window, text } + } + pub fn draw(&mut self) -> Result<(), &'static str> { + let mut window = self.window.lock(); + + window.draw_rectangle(DEFAULT_WINDOW_COLOR)?; + + window.display_window_title(DEFAULT_TEXT_COLOR, DEFAULT_BORDER_COLOR)?; + print_string( + &mut window, + &self.text.pos, + &self.text.text, + self.text.fg_color, + self.text.bg_color, + )?; + + Ok(()) + } +} + +/// Prints a line of string to the `Window` +/// +/// * `window` - Window we are printing to. +/// * `position` - This indicates where line of text will be. +/// * `slice` - Text we are printing +/// * `fg_color` - Foreground color of the text +/// * `bg_color` - Background color of the text +pub fn print_string( + window: &mut Window, + position: &RelativePos, + slice: &str, + fg_color: Color, + bg_color: Color, +) -> Result<(), &'static str> { + let slice = slice.as_bytes(); + // FIXME: Text starts overlapping if x + text len is bigger than window.width + let start_x = position.x; + let start_y = position.y; + + let mut x_index = 0; + let mut row_controller = 0; + let mut char_index = 0; + let mut char_color_on_x_axis = x_index; + + let mut window_rect = window.rect; + window_rect.set_position(start_x, start_y); + let mut row_of_pixels = FramebufferRowChunks::get_exact_row( + &mut window.frame_buffer.buffer, + window_rect, + window_rect.width, + start_y as usize, + )?; + + loop { + let y = start_y + row_controller as u32; + if x_index % CHARACTER_WIDTH == 0 { + char_color_on_x_axis = 0; + } + let color = if char_color_on_x_axis >= 1 { + let index = char_color_on_x_axis - 1; + // TODO: Stop indexing here + let char_font = FONT_BASIC[slice[char_index] as usize][row_controller]; + char_color_on_x_axis += 1; + if get_bit(char_font, index) != 0 { + fg_color + } else { + bg_color + } + } else { + char_color_on_x_axis += 1; + bg_color + }; + + // Altough bit ugly, this works quite well with our current way of rendering fonts + if let Some(pixel) = row_of_pixels.next() { + *pixel = color; + } + + x_index += 1; + if x_index == CHARACTER_WIDTH || x_index % CHARACTER_WIDTH == 0 { + if slice.len() >= 1 && char_index < slice.len() - 1 { + char_index += 1; + } + + if x_index >= CHARACTER_WIDTH * slice.len() + && x_index % (CHARACTER_WIDTH * slice.len()) == 0 + { + row_of_pixels = FramebufferRowChunks::get_exact_row( + &mut window.frame_buffer.buffer, + window_rect, + window_rect.width, + y as usize, + )?; + row_controller += 1; + char_index = 0; + x_index = 0; + } + + if row_controller == CHARACTER_HEIGHT { + break; + } + } + } + Ok(()) +} +fn get_bit(char_font: u8, i: usize) -> u8 { + char_font & (0x80 >> i) +} + +pub struct TextDisplayInfo { + width: usize, + height: usize, + pos: RelativePos, + next_col: usize, + next_line: usize, + text: String, + fg_color: Color, + bg_color: Color, +} + +impl TextDisplayInfo { + pub fn new( + width: usize, + height: usize, + pos: RelativePos, + next_col: usize, + next_line: usize, + text: String, + fg_color: Color, + bg_color: Color, + ) -> Self { + Self { + width, + height, + pos, + next_col, + next_line, + text, + fg_color, + bg_color, + } + } + + pub fn append_char(&mut self, char: char) { + self.text.push(char); + } +} + +/// Position that is relative to a `Window` +#[derive(Debug, Clone, Copy)] +pub struct RelativePos { + pub x: u32, + pub y: u32, +} + +impl RelativePos { + pub fn new(x: u32, y: u32) -> Self { + Self { x, y } + } + + pub fn to_1d_pos(&self, target_stride: u32) -> usize { + ((target_stride * self.y) + self.x) as usize + } +} + +/// Position that is relative to the screen +#[derive(Debug, Clone, Copy)] +pub struct ScreenPos { + pub x: i32, + pub y: i32, +} + +impl ScreenPos { + pub fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + + pub fn to_1d_pos(&self) -> usize { + ((SCREEN_WIDTH as i32 * self.y) + self.x) as usize + } +} + +impl Add for ScreenPos { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl Sub for ScreenPos { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl Add for ScreenPos { + type Output = Self; + + fn add(self, other: Rect) -> Self { + Self { + x: self.x + other.x as i32, + y: self.y + other.y as i32, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Rect { + pub width: usize, + pub height: usize, + pub x: isize, + pub y: isize, +} + +impl Rect { + fn new(width: usize, height: usize, x: isize, y: isize) -> Rect { + Rect { + width, + height, + x, + y, + } + } + + pub fn to_screen_pos(&self) -> ScreenPos { + ScreenPos { + x: self.x as i32, + y: self.y as i32, + } + } + + pub fn to_relative_pos(&self) -> RelativePos { + let x = core::cmp::max(0, self.x) as u32; + let y = core::cmp::min(SCREEN_HEIGHT as isize, self.y) as u32; + RelativePos { x, y } + } + + pub fn set_position(&mut self, x: u32, y: u32) { + self.x = x as isize; + self.y = y as isize; + } + + fn x_plus_width(&self) -> isize { + self.x + self.width as isize + } + + fn y_plus_height(&self) -> isize { + self.y + self.height as isize + } + + fn detect_collision(&self, other: &Rect) -> bool { + self.x < other.x_plus_width() + && self.x_plus_width() > other.x + && self.y < other.y_plus_height() + && self.y_plus_height() > other.y + } + + pub fn left_side_out(&self) -> bool { + self.x < 0 + } + + pub fn right_side_out(&self) -> bool { + self.x + self.width as isize > SCREEN_WIDTH as isize + } + + pub fn bottom_side_out(&self) -> bool { + self.y + self.height as isize > SCREEN_HEIGHT as isize + } + + /// Creates a new `Rect` from visible parts of itself. + pub fn visible_rect(&self) -> Rect { + let mut x = self.x; + let y = self.y; + let mut width = self.width as isize; + let mut height = self.height as isize; + if self.left_side_out() { + x = 0; + width = self.x_plus_width(); + } else if self.right_side_out() { + x = self.x; + let gap = (self.x + self.width as isize) - SCREEN_WIDTH as isize; + width = self.width as isize - gap; + } + if self.bottom_side_out() { + let gap = (self.y + self.height as isize) - SCREEN_HEIGHT as isize; + height = self.height as isize - gap; + } + let visible_rect = Rect::new(width as usize, height as usize, x, y); + visible_rect + } +} + +pub struct VirtualFrameBuffer { + width: usize, + height: usize, + buffer: BorrowedSliceMappedPages, +} + +impl VirtualFrameBuffer { + pub fn new(width: usize, height: usize) -> Result { + let kernel_mmi_ref = + memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; + let size = width * height * core::mem::size_of::(); + let pages = memory::allocate_pages_by_bytes(size) + .ok_or("could not allocate pages for a new framebuffer")?; + let mapped_buffer = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages(pages, PteFlags::new().valid(true).writable(true))?; + Ok(VirtualFrameBuffer { + width, + height, + buffer: mapped_buffer + .into_borrowed_slice_mut(0, width * height) + .map_err(|(_mp, s)| s)?, + }) + } + + fn copy_window_into_main_vbuffer(&mut self, window: &mut MutexGuard) { + let window_screen = window.rect.visible_rect(); + let window_stride = window.frame_buffer.width as usize; + + if let Some(screen_rows) = + FramebufferRowChunks::new(&mut self.buffer, window_screen, self.width) + { + // To handle rendering when the window is partially outside the screen we use relative version of visible rect + let relative_visible_rect = window.relative_visible_rect(); + if let Some(window_rows) = FramebufferRowChunks::new( + &mut window.frame_buffer.buffer, + relative_visible_rect, + window_stride, + ) { + for (screen_row, window_row) in screen_rows.zip(window_rows) { + screen_row.copy_from_slice(window_row); + } + } + } + } + + pub fn blank(&mut self) { + for pixel in self.buffer.iter_mut() { + *pixel = 0x000000; + } + } +} + +/// Physical framebuffer we use for final rendering to the screen. +pub struct PhysicalFrameBuffer { + width: usize, + height: usize, + stride: usize, + buffer: BorrowedSliceMappedPages, +} +impl PhysicalFrameBuffer { + fn init_front_buffer() -> Result { + let graphic_info = + multicore_bringup::get_graphic_info().ok_or("Failed to get graphic info")?; + if graphic_info.physical_address() == 0 { + return Err("wrong physical address for porthole"); + } + let vesa_display_phys_start = + PhysicalAddress::new(graphic_info.physical_address() as usize) + .ok_or("Invalid address")?; + let buffer_width = graphic_info.width() as usize; + let buffer_height = graphic_info.height() as usize; + // We are assuming a pixel is 4 bytes big + let stride = graphic_info.bytes_per_scanline() / 4; + + let framebuffer = PhysicalFrameBuffer::new( + buffer_width, + buffer_height, + stride as usize, + vesa_display_phys_start, + )?; + Ok(framebuffer) + } + + pub fn new( + width: usize, + height: usize, + stride: usize, + physical_address: PhysicalAddress, + ) -> Result { + let kernel_mmi_ref = + memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; + let size = width * height * core::mem::size_of::(); + let pages = memory::allocate_pages_by_bytes(size) + .ok_or("could not allocate pages for a new framebuffer")?; + + let mapped_framebuffer = { + let mut flags: PteFlagsArch = PteFlags::new().valid(true).writable(true).into(); + + #[cfg(target_arch = "x86_64")] + { + let use_pat = page_attribute_table::init().is_ok(); + if use_pat { + flags = flags.pat_index( + page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index(), + ); + info!("Using PAT write-combining mapping for real physical framebuffer memory"); + } else { + flags = flags.device_memory(true); + info!("Falling back to cache-disable mapping for real physical framebuffer memory"); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + flags = flags.device_memory(true); + } + + let frames = memory::allocate_frames_by_bytes_at(physical_address, size) + .map_err(|_e| "Couldn't allocate frames for the final framebuffer")?; + let fb_mp = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages_to(pages, frames, flags)?; + debug!("Mapped real physical framebuffer: {fb_mp:?}"); + fb_mp + }; + Ok(PhysicalFrameBuffer { + width, + height, + stride, + buffer: mapped_framebuffer + .into_borrowed_slice_mut(0, width * height) + .map_err(|(_mp, s)| s)?, + }) + } +} + +/// Our mouse image is [`MOUSE_POINTER_IMAGE`] column major 2D array +/// This type returns us row major, 1D vec of that image +pub struct MouseImageRowIterator<'a> { + /// Mouse image [`MOUSE_POINTER_IMAGE`] + mouse_image: &'a [[u32; 18]; 11], + /// Rect of our mouse + bounding_box: Rect, + /// Since image is column major we will iterate will use + /// individual columns to create a row, think of it as y axis + current_column: usize, + /// Used to traverse image in x axis + current_row: usize, +} + +impl<'a> MouseImageRowIterator<'a> { + fn new(mouse_image: &'a [[u32; 18]; 11], bounding_box: Rect) -> Self { + Self { + mouse_image, + bounding_box, + current_column: 0, + current_row: 0, + } + } +} + +impl<'a> Iterator for MouseImageRowIterator<'a> { + type Item = Vec; + + fn next(&mut self) -> Option> { + // We start from MOUSE_POINTER_IMAGE[0][0], get the color on that index push it to our `row` + // then move to MOUSE_POINTER_IMAGE[1][0] do the same thing + // until we hit `bounding_box.width -1` then we reset our `current_column` to `0` and increase + // our `current_row` by `1` + if self.current_row < self.bounding_box.height - 1 { + let mut row = Vec::new(); + while self.current_column < self.bounding_box.width { + let color = self + .mouse_image + .get(self.current_column)? + .get(self.current_row)?; + + row.push(*color); + self.current_column += 1; + if self.current_column == self.bounding_box.width - 1 { + self.current_column = 0; + break; + } + } + self.current_row += 1; + Some(row) + } else { + None + } + } +} + +/// From given mutable `Framebuffer` slice and `Rect` returns +/// rows of slices. +/// +/// Think `Framebuffer` as a big cake and `Rect` as a smaller cake within the `Framebuffer` +/// this returns row of mutable slices from that smaller cake. +pub struct FramebufferRowChunks<'a, T: 'a> { + /// Framebuffer we used to get the `rows` from + fb: &'a mut [T], + /// This let's us decide where and how big of a row we want to get + rect: Rect, + /// Amount of pixels in a line of `Framebuffer` + stride: usize, + /// Where we start the row + row_index_beg: usize, + /// Where we end the row + row_index_end: usize, + /// The index of the row we are getting + current_column: usize, + _marker: PhantomData<&'a mut T>, +} + +impl<'a, T: 'a> FramebufferRowChunks<'a, T> { + #[inline] + pub fn new(slice: &'a mut [T], rect: Rect, stride: usize) -> Option { + if rect.width <= stride { + let current_column = rect.y as usize; + let row_index_beg = (stride * current_column) + rect.x as usize; + let row_index_end = (stride * current_column) + rect.x_plus_width() as usize; + Some(Self { + fb: slice, + rect, + stride, + row_index_beg, + row_index_end, + current_column, + _marker: PhantomData, + }) + } else { + None + } + } + + /// Returns a single `IterMut` from specified `row` of the framebuffer + /// + /// * `row` - Specifies which row will be returned from this function + pub fn get_exact_row( + slice: &'a mut [T], + rect: Rect, + stride: usize, + row: usize, + ) -> Result, &'static str> { + let mut rect = rect; + rect.y = row as isize; + rect.height = 1; + let mut rows = FramebufferRowChunks::new(slice, rect, stride) + .ok_or("Couldn't create `FramebufferRowChunks` from given rect")?; + let row_iterator = rows.next().ok_or("Error created empty row")?.iter_mut(); + Ok(row_iterator) + } + +} + +impl<'a, T> Iterator for FramebufferRowChunks<'a, T> { + type Item = &'a mut [T]; + + fn next(&mut self) -> Option<&'a mut [T]> { + if self.current_column < self.rect.y_plus_height() as usize { + // To not fight borrow checker we do this little trick here + let slice = core::mem::replace(&mut self.fb, &mut []); + + if slice.len() < self.row_index_end { + return None; + } + self.current_column += 1; + + let (row, rest_of_slice) = slice.split_at_mut(self.row_index_end); + + // We want to keep rest of the slice + self.fb = rest_of_slice; + if let Some(chunk) = row.get_mut(self.row_index_beg..self.row_index_end) { + // Because we are taking part of a slice we need this gap to be added to + // `row_index_beg` and `row_index_end` so we can correctly index the framebuffer slice + let gap = self.stride - self.row_index_end; + self.row_index_beg = self.row_index_beg + gap; + self.row_index_end = self.row_index_end + gap; + return Some(chunk); + } else { + None + } + } else { + None + } + } +} + +pub fn main(_args: Vec) -> Result { + let mouse_consumer = Queue::with_capacity(100); + let mouse_producer = mouse_consumer.clone(); + let key_consumer = Queue::with_capacity(100); + let key_producer = mouse_consumer.clone(); + WindowManager::init()?; + device_manager::init(key_producer, mouse_producer) + .or(Err("Failed to initialize device manager"))?; + + let _task_ref = match spawn::new_task_builder(port_loop, (mouse_consumer, key_consumer)) + .name("port_loop".to_string()) + .pin_on_core(0) + .spawn() + { + Ok(task_ref) => task_ref, + Err(err) => { + log::error!("{}", err); + log::error!("failed to spawn shell"); + return Err("failed to spawn shell"); + } + }; + + task::get_my_current_task() + .ok_or("Failed to get the current task")? + .block() + .or(Err("Failed to block the current task"))?; + scheduler::schedule(); + + loop { + log::warn!("BUG: blocked shell task was scheduled in unexpectedly"); + } +} + +#[derive(PartialEq, Eq)] +pub enum Holding { + Background, + Nothing, + Window(usize), +} + +impl Holding { + fn nothing(&self) -> bool { + *self == Holding::Nothing + } + + fn backgrond(&self) -> bool { + *self == Holding::Background + } + + fn window(&self) -> bool { + !self.nothing() && !self.backgrond() + } +} +pub struct WindowManager { + windows: Vec>>, + window_rendering_order: Vec, + v_framebuffer: VirtualFrameBuffer, + p_framebuffer: PhysicalFrameBuffer, + pub mouse: Rect, + prev_mouse_pos: ScreenPos, + mouse_holding: Holding, + /// Holds the index of the active window/last element in the `window_rendering_order` + active_window_index: usize, +} + +impl WindowManager { + fn init() -> Result<(), &'static str> { + let p_framebuffer = PhysicalFrameBuffer::init_front_buffer()?; + let v_framebuffer = VirtualFrameBuffer::new(p_framebuffer.width, p_framebuffer.height)?; + // FIXME: Don't use magic numbers, + let mouse = Rect::new(11, 18, 200, 200); + + let window_manager = WindowManager { + windows: Vec::new(), + window_rendering_order: Vec::new(), + v_framebuffer, + p_framebuffer, + mouse, + prev_mouse_pos: mouse.to_screen_pos(), + mouse_holding: Holding::Nothing, + active_window_index: 0, + }; + WINDOW_MANAGER.call_once(|| Mutex::new(window_manager)); + Ok(()) + } + + fn new_window( + &mut self, + rect: &Rect, + title: Option, + ) -> Result>, &'static str> { + let len = self.windows.len(); + + self.window_rendering_order.push(len); + let window = Window::new( + *rect, + VirtualFrameBuffer::new(rect.width, rect.height)?, + title, + ); + let arc_window = Arc::new(Mutex::new(window)); + let returned_window = arc_window.clone(); + self.windows.push(arc_window); + Ok(returned_window) + } + + fn draw_windows(&mut self) { + for order in self.window_rendering_order.iter() { + self.v_framebuffer + .copy_window_into_main_vbuffer(&mut self.windows[*order].lock()); + // TODO: Stop indexing ^ here + } + } + + fn draw_mouse(&mut self) { + let visible_mouse = self.mouse.visible_rect(); + + if let Some(screen_rows) = + FramebufferRowChunks::new(&mut self.v_framebuffer.buffer, visible_mouse, SCREEN_WIDTH) + { + let mouse_image = MouseImageRowIterator::new(&MOUSE_POINTER_IMAGE, visible_mouse); + for (screen_row, mouse_image_row) in screen_rows.zip(mouse_image) { + for (screen_pixel, mouse_pixel) in screen_row.iter_mut().zip(mouse_image_row.iter()) + { + if mouse_pixel != &0xFF0000 { + *screen_pixel = *mouse_pixel; + } + } + } + } + } + + pub fn set_mouse_pos(&mut self, screen_positon: &ScreenPos) { + self.mouse.x = screen_positon.x as isize; + self.mouse.y = screen_positon.y as isize; + } + + fn update(&mut self) { + self.v_framebuffer.blank(); + self.draw_windows(); + self.draw_mouse(); + } + + fn calculate_next_mouse_pos( + &self, + current_position: ScreenPos, + next_position: ScreenPos, + ) -> ScreenPos { + let mut new_pos = next_position + current_position; + + // handle left + new_pos.x = core::cmp::max(new_pos.x, 0); + // handle right + new_pos.x = core::cmp::min( + new_pos.x, + self.v_framebuffer.width as i32 - MOUSE_VISIBLE_GAP, + ); + + // handle top + new_pos.y = core::cmp::max(new_pos.y, 0); + // handle bottom + new_pos.y = core::cmp::min( + new_pos.y, + self.v_framebuffer.height as i32 - MOUSE_VISIBLE_GAP, + ); + + new_pos + } + + fn update_mouse_position(&mut self, screen_pos: ScreenPos) { + self.prev_mouse_pos = self.mouse.to_screen_pos(); + let new_pos = self.calculate_next_mouse_pos(self.mouse.to_screen_pos(), screen_pos); + + self.set_mouse_pos(&new_pos); + } + + fn drag_windows(&mut self, screen_position: ScreenPos, mouse_event: &MouseEvent) { + if !mouse_event.buttons.left() { + self.mouse_holding = Holding::Nothing; + } + if mouse_event.buttons.left() { + match self.mouse_holding { + Holding::Background => {} + Holding::Nothing => { + // We are cloning this value because we will use it to iterate through our windows while editing the original one + let rendering_order = self.window_rendering_order.clone(); + // `iter_index` = index of the window in `self.window_rendering_order` + // `window_index` = index of the window in `self.windows` + for (iter_index, &window_index) in rendering_order.iter().enumerate().rev() { + let window = &mut self.windows[window_index]; + if window.lock().rect.detect_collision(&self.mouse) { + // If colliding window is not active one make it active + // we first remove colliding window from it's position in + // window_rendering_order, then push it to the back of + // window_rendering_order, this way we don't have to do any special sorting + if window_index != self.active_window_index { + self.active_window_index = window_index; + self.window_rendering_order.remove(iter_index); + self.window_rendering_order.push(window_index); + } + // If user is holding the window from it's title border pos + // it means user wants to move the window + if window + .lock() + .dynamic_title_border_pos() + .detect_collision(&self.mouse) + { + self.mouse_holding = Holding::Window(window_index); + } + break; + } + self.mouse_holding = Holding::Nothing; + } + // If couldn't hold onto anything we must have hold onto background + if self.mouse_holding.nothing() { + self.mouse_holding = Holding::Background + } + } + Holding::Window(i) => { + // These calculations are required because we do want finer control + // over a window's movement. + let prev_mouse_pos = self.prev_mouse_pos; + let next_mouse_pos = + self.calculate_next_mouse_pos(prev_mouse_pos, screen_position); + let window = &mut self.windows[i]; + let window_rect = window.lock().rect; + let diff = next_mouse_pos - prev_mouse_pos; + let mut new_pos = diff + window_rect.to_screen_pos(); + + //handle left + if (new_pos.x + (window_rect.width as i32 - WINDOW_VISIBLE_GAP as i32)) < 0 { + new_pos.x = -(window_rect.width as i32 - WINDOW_VISIBLE_GAP); + } + + //handle right + if (new_pos.x + WINDOW_VISIBLE_GAP) > self.v_framebuffer.width as i32 { + new_pos.x = SCREEN_WIDTH as i32 - WINDOW_VISIBLE_GAP + } + + //handle top + if new_pos.y < 0 { + new_pos.y = 0 + } + + // handle bottom + if new_pos.y + WINDOW_VISIBLE_GAP > self.v_framebuffer.height as i32 { + new_pos.y = (SCREEN_HEIGHT as i32 - WINDOW_VISIBLE_GAP) as i32; + } + + window.lock().set_screen_pos(&new_pos); + } + } + } else if mouse_event.buttons.right() { + let rendering_o = self.window_rendering_order.clone(); + for &i in rendering_o.iter().rev() { + let window = &mut self.windows[i]; + if window.lock().rect.detect_collision(&Rect::new( + self.mouse.width, + self.mouse.height, + self.mouse.x, + self.mouse.y, + )) { + window + .lock() + .resize_window(screen_position.x, screen_position.y); + window.lock().reset_drawable_area(); + window.lock().reset_title_pos_and_border(); + window.lock().resized = true; + break; + } + } + } + } + + fn render(&mut self) { + self.p_framebuffer + .buffer + .copy_from_slice(&self.v_framebuffer.buffer); + } +} + +pub struct Window { + rect: Rect, + pub frame_buffer: VirtualFrameBuffer, + resized: bool, + title: Option, + title_border: Option, + title_pos: Option, + drawable_area: Option, +} + +impl Window { + fn new(rect: Rect, frame_buffer: VirtualFrameBuffer, title: Option) -> Window { + Window { + rect, + frame_buffer, + resized: false, + title, + title_border: None, + title_pos: None, + drawable_area: None, + } + } + + pub fn display_window_title( + &mut self, + fg_color: Color, + bg_color: Color, + ) -> Result<(), &'static str> { + if let Some(title) = self.title.clone() { + let slice = title.as_str(); + let title_pos = self.title_pos(&slice.len()); + print_string(self, &title_pos, slice, fg_color, bg_color)?; + } + Ok(()) + } + pub fn width(&self) -> usize { + self.rect.width + } + + pub fn height(&self) -> usize { + self.rect.height + } + + pub fn screen_pos(&self) -> ScreenPos { + let screen_pos = ScreenPos::new(self.rect.x as i32, self.rect.y as i32); + screen_pos + } + + pub fn set_screen_pos(&mut self, screen_position: &ScreenPos) { + self.rect.x = screen_position.x as isize; + self.rect.y = screen_position.y as isize; + } + + pub fn resize_window(&mut self, width: i32, height: i32) { + let new_width = self.width() + width as usize; + let new_height = self.height() + height as usize; + if new_width > 200 && new_height > 200 { + self.rect.width = new_width; + self.rect.height = new_height; + } + } + + pub fn reset_drawable_area(&mut self) { + self.drawable_area = None; + } + + pub fn reset_title_pos_and_border(&mut self) { + self.title_border = None; + self.title_pos = None; + } + + /// Returns Window's border area width and height with 0 as position + pub fn title_border(&mut self) -> Rect { + let border = + self.title_border + .get_or_insert(Rect::new(self.rect.width, TITLE_BAR_HEIGHT, 0, 0)); + *border + } + + /// Return's title border's position in screen coordinates + pub fn dynamic_title_border_pos(&self) -> Rect { + let mut rect = self.rect; + rect.height = TITLE_BAR_HEIGHT; + rect + } + + /// Return's drawable area + pub fn drawable_area(&mut self) -> Rect { + let border = self.title_border(); + let drawable_area = self.drawable_area.get_or_insert({ + let x = 0; + let y = border.height; + let width = border.width; + let height = self.rect.height - y; + let drawable_area = Rect::new(width, height, x, y as isize); + drawable_area + }); + *drawable_area + } + + /// From given title length returns center position of the title border + pub fn title_pos(&mut self, title_length: &usize) -> RelativePos { + let border = self.title_border(); + let relative_pos = self.title_pos.get_or_insert({ + let pos = (border.width - (title_length * CHARACTER_WIDTH)) / 2; + let relative_pos = RelativePos::new(pos as u32, 0); + relative_pos + }); + *relative_pos + } + + pub fn draw_title_border(&mut self) { + let border = self.title_border(); + if let Some(rows) = FramebufferRowChunks::new( + &mut self.frame_buffer.buffer, + border, + self.frame_buffer.width, + ) { + for row in rows { + for pixel in row { + *pixel = DEFAULT_BORDER_COLOR; + } + } + } + } + + fn should_resize_framebuffer(&mut self) -> Result<(), &'static str> { + if self.resized { + self.resize_framebuffer()?; + self.resized = false; + } + Ok(()) + } + + /// Draws the rectangular shape representing the `Window` + pub fn draw_rectangle(&mut self, color: Color) -> Result<(), &'static str> { + self.should_resize_framebuffer()?; + + for pixel in self.frame_buffer.buffer.iter_mut() { + *pixel = color; + } + self.draw_title_border(); + Ok(()) + } + + /// Resizes framebuffer after to Window's width and height + fn resize_framebuffer(&mut self) -> Result<(), &'static str> { + self.frame_buffer = VirtualFrameBuffer::new(self.rect.width, self.rect.height).or(Err( + "Unable to resize framebuffer to current width and height", + ))?; + Ok(()) + } + + /// Returns visible part of self's `rect` with relative bounds applied + /// e.g if visible rect is `Rect{width: 358, height: 400, x: 0, y: 0}` + /// this will return `Rect{width: 358, height: 400, x: 42, y: 0}` + pub fn relative_visible_rect(&self) -> Rect { + let mut bounding_box = self.rect.visible_rect(); + bounding_box.x = 0; + if self.rect.left_side_out() { + bounding_box.x = (self.rect.width - bounding_box.width) as isize; + } + bounding_box.y = 0; + bounding_box + } +} + +fn port_loop( + (key_consumer, mouse_consumer): (Queue, Queue), +) -> Result<(), &'static str> { + let window_manager = WINDOW_MANAGER.get().ok_or("Unable to get WindowManager")?; + + let window_2 = window_manager + .lock() + .new_window(&Rect::new(400, 400, 500, 20), Some(format!("Basic")))?; + let drawable_area = window_2.lock().drawable_area(); + let text = TextDisplayInfo { + width: drawable_area.width, + height: drawable_area.height, + pos: RelativePos::new(drawable_area.x as u32, drawable_area.y as u32), + next_col: 0, + next_line: 0, + text: "Helloaaaaaaaaaaaa World".to_string(), + fg_color: DEFAULT_TEXT_COLOR, + bg_color: DEFAULT_WINDOW_COLOR, + }; + let mut app = App::new(window_2, text); + + let window_3 = window_manager + .lock() + .new_window(&Rect::new(400, 100, 0, 0), Some(format!("Fps Counter")))?; + let mut fps_counter = FpsCounter::new(window_3)?; + + loop { + let event_opt = key_consumer + .pop() + .or_else(|| mouse_consumer.pop()) + .or_else(|| { + scheduler::schedule(); + None + }); + + if let Some(event) = event_opt { + match event { + Event::MouseMovementEvent(ref mouse_event) => { + let movement = &mouse_event.movement; + let mut x = (movement.x_movement as i8) as isize; + let mut y = (movement.y_movement as i8) as isize; + while let Some(next_event) = mouse_consumer.pop() { + match next_event { + Event::MouseMovementEvent(ref next_mouse_event) => { + if next_mouse_event.movement.scroll_movement + == mouse_event.movement.scroll_movement + && next_mouse_event.buttons.left() == mouse_event.buttons.left() + && next_mouse_event.buttons.right() + == mouse_event.buttons.right() + && next_mouse_event.buttons.fourth() + == mouse_event.buttons.fourth() + && next_mouse_event.buttons.fifth() + == mouse_event.buttons.fifth() + { + x += (next_mouse_event.movement.x_movement as i8) as isize; + y += (next_mouse_event.movement.y_movement as i8) as isize; + } + } + + _ => { + break; + } + } + } + if x != 0 || y != 0 { + window_manager + .lock() + .update_mouse_position(ScreenPos::new(x as i32, -(y as i32))); + } + window_manager + .lock() + .drag_windows(ScreenPos::new(x as i32, -(y as i32)), &mouse_event); + } + _ => (), + } + } + fps_counter.run()?; + app.draw()?; + window_manager.lock().update(); + window_manager.lock().render(); + // app.draw()?; + //fps_counter.run()?; + } + Ok(()) +}