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<u16>;
pub type ScreenPosition = geometry::Point<u16>;
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<u8>,
pub background: Vec<u8>,
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_rect(parameters, layer),
0x60 => self.draw_line_1bit(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,
])
}
}
}