mod sprite_data; mod draw_line; mod draw_rect; mod draw_sprite; pub use sprite_data::*; use geometry::HasDimensions; use phosphor::*; use std::cmp::{min, max, Ordering}; use std::iter::zip; pub type ScreenDimensions = geometry::Dimensions; pub type ScreenPosition = geometry::Point; pub type Plane = [u8; 8]; pub type Sprite = [[u8; 8]; 8]; const TRANSPARENT: u8 = 0x08; const FLIP_DIAGONAL: u8 = 0x04; const FLIP_VERTICAL: u8 = 0x02; const FLIP_HORIZONTAL: u8 = 0x01; const NEGATIVE: u8 = 0x80; const VERTICAL: u8 = 0x40; #[derive(Copy, Clone)] pub enum ScreenLayer { Background, Foreground } macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } pub struct ScreenDevice { pub wake_flag: bool, /// Each byte represents a screen pixel, left-to-right and top-to-bottom. // Only the bottom four bits of each byte are used. pub foreground: Vec, pub background: Vec, pub dirty: bool, pub resizable: bool, pub cursor: ScreenPosition, pub vector: ScreenPosition, pub dimensions: ScreenDimensions, pub palette_high: u8, pub palette: [Colour; 16], pub sprite_buffer: SpriteBuffer, } impl ScreenDevice { pub fn new() -> Self { Self { wake_flag: false, foreground: Vec::new(), background: Vec::new(), dirty: false, resizable: true, cursor: ScreenPosition::ZERO, vector: ScreenPosition::ZERO, dimensions: ScreenDimensions::ZERO, palette_high: 0, palette: [Colour::BLACK; 16], sprite_buffer: SpriteBuffer::new(), } } pub fn set_size(&mut self) { self.resizable = false; self.resize(self.dimensions); } // Resize the screen buffers while preserving the current content. pub fn resize(&mut self, dimensions: ScreenDimensions) { let old_width = self.dimensions.width as usize; let old_height = self.dimensions.height as usize; let new_width = dimensions.width as usize; let new_height = dimensions.height as usize; let new_area = dimensions.area_usize(); let y_range = 0..min(old_height, new_height); match new_width.cmp(&old_width) { Ordering::Less => { for y in y_range { let from = y * old_width; let to = y * new_width; let len = new_width; self.foreground.copy_within(from..from+len, to); self.background.copy_within(from..from+len, to); } self.foreground.resize(new_area, 0); self.background.resize(new_area, 0); }, Ordering::Greater => { self.foreground.resize(new_area, 0); self.background.resize(new_area, 0); for y in y_range.rev() { let from = y * old_width; let to = y * new_width; let len = old_width; self.foreground.copy_within(from..from+len, to); self.background.copy_within(from..from+len, to); self.foreground[to+len..to+new_width].fill(0); self.background[to+len..to+new_width].fill(0); } }, Ordering::Equal => { self.foreground.resize(new_area, 0); self.background.resize(new_area, 0); }, }; self.dimensions = dimensions; self.dirty = true; self.wake_flag = true; } pub fn render(&mut self, buffer: &mut Buffer) { // Pre-calculate a lookup table for the colour palette let mut lookup = [Colour::BLACK; 256]; for (i, c) in lookup.iter_mut().enumerate() { match i > 0x0f { true => *c = self.palette[i >> 4], false => *c = self.palette[i & 0x0f], } }; // Prepare values let b_width = buffer.width() as usize; let b_height = buffer.height() as usize; let s_width = self.dimensions.width() as usize; let s_height = self.dimensions.height() as usize; // Write colours to the buffer if b_width == s_width && b_height == s_height { let screen_iter = zip(&self.background, &self.foreground); let buffer_iter = buffer.as_mut_slice(); for (b, (bg, fg)) in zip(buffer_iter, screen_iter) { *b = lookup[(fg << 4 | bg) as usize]; } // Write colours to the buffer when the size of the buffer is wrong } else { let width = min(b_width, s_width); let height = min(b_height, s_height); let width_excess = b_width.saturating_sub(width); let b_slice = &mut buffer.as_mut_slice(); let mut bi = 0; let mut si = 0; for _ in 0..height { let b_iter = &mut b_slice[bi..bi+width]; let s_iter = zip( &self.background[si..si+width], &self.foreground[si..si+width], ); for (b, (bg, fg)) in zip(b_iter, s_iter) { *b = lookup[(fg << 4 | bg) as usize]; } b_slice[bi+width..bi+width+width_excess].fill(lookup[0]); bi += b_width; si += s_width; } b_slice[bi..].fill(lookup[0]); } self.dirty = false; } pub fn set_palette_high(&mut self, val: u8) { self.palette_high = val; } pub fn set_palette_low(&mut self, val: u8) { let index = (self.palette_high >> 4) as usize; let red = (self.palette_high & 0x0f) * 17; let green = (val >> 4) * 17; let blue = (val & 0x0f) * 17; self.palette[index] = Colour::from_rgb(red, green, blue); self.dirty = true; } pub fn shunt(&mut self, val: u8) { let negative = test!(val, NEGATIVE); let vertical = test!(val, VERTICAL); let dist = (val & 0x3f) as u16; match (negative, vertical) { (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist), (false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist), ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist), ( true, true) => self.cursor.y = self.cursor.y.wrapping_sub(dist), }; } pub fn draw(&mut self, val: u8) { let operation = val & 0x70; let parameters = val & 0x0f; let layer = match val & 0x80 != 0 { true => ScreenLayer::Foreground, false => ScreenLayer::Background }; match operation { 0x00 => self.draw_pixel(parameters, layer, self.cursor), 0x10 => self.draw_sprite_1bit(parameters, layer), 0x20 => self.fill_layer(parameters, layer), 0x30 => self.draw_sprite_2bit(parameters, layer), 0x40 => self.draw_line(parameters, layer), 0x50 => self.draw_line_1bit(parameters, layer), 0x60 => self.draw_rect(parameters, layer), 0x70 => self.draw_rect_1bit(parameters, layer), _ => unreachable!(), }; self.dirty = true; self.vector = self.cursor; } // Draw a single pixel of a single colour fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) { let dim = self.dimensions; if !dim.contains_point(point) || colour > 0xf { return } let index = point.x as usize + ((dim.width as usize) * (point.y as usize)); match layer { ScreenLayer::Background => self.background[index] = colour, ScreenLayer::Foreground => self.foreground[index] = colour, }; } // Fill an entire screen layer with a single colour fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) { match layer { ScreenLayer::Background => self.background.fill(colour), ScreenLayer::Foreground => self.foreground.fill(colour), } } /// Returns [x0, y0, x1, y1], ensuring that x0 <= x1 and y0 <= y1 fn find_vector_bounding_box(&self) -> Option<[usize; 4]> { macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} let [p0, p1] = [self.cursor, self.vector]; let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ]; let right = self.dimensions.width.saturating_sub(1); let bottom = self.dimensions.height.saturating_sub(1); if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 { None } else { Some([ if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize, if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize, if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize, if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize, ]) } } }