use crate::{
host::{FrameBuffer, FrameBufferSource},
zx::{
constants::{
BORDER_COLS, BORDER_ROWS, CLOCKS_PER_COL, PIXELS_PER_CLOCK, SCREEN_HEIGHT, SCREEN_WIDTH,
},
machine::ZXMachine,
video::colors::{ZXBrightness, ZXColor},
},
};
#[derive(Clone, Copy)]
struct BeamInfo {
line: usize,
pixel: usize,
color: ZXColor,
}
impl BeamInfo {
fn first_pixel(color: ZXColor) -> BeamInfo {
BeamInfo::new(0, 0, color)
}
fn new(line: usize, pixel: usize, color: ZXColor) -> BeamInfo {
BeamInfo { line, pixel, color }
}
fn reset(&mut self) {
self.line = 0;
self.pixel = 0;
}
}
pub struct ZXBorder<FB: FrameBuffer> {
machine: ZXMachine,
buffer: FB,
beam_last: BeamInfo,
border_changed: bool,
beam_block: bool,
}
impl<FB: FrameBuffer> ZXBorder<FB> {
pub fn new(machine: ZXMachine, context: FB::Context) -> Self {
ZXBorder {
machine,
buffer: FB::new(
SCREEN_WIDTH,
SCREEN_HEIGHT,
FrameBufferSource::Border,
context,
),
beam_last: BeamInfo::first_pixel(ZXColor::White),
border_changed: true,
beam_block: false,
}
}
fn next_border_pixel(&self, clocks: usize) -> (usize, usize, bool) {
let specs = self.machine.specs();
let clocks_origin = specs.clocks_first_pixel
- 8 * BORDER_ROWS * specs.clocks_line
- BORDER_COLS * CLOCKS_PER_COL
+ specs.clocks_ula_beam_shift;
if clocks < clocks_origin {
return (0, 0, false);
}
let clocks = clocks - clocks_origin;
let mut line = clocks / specs.clocks_line;
let mut pixel = ((clocks % specs.clocks_line) + 1) * PIXELS_PER_CLOCK;
if pixel - PIXELS_PER_CLOCK >= SCREEN_WIDTH {
pixel = 0;
line += 1;
}
if line >= SCREEN_HEIGHT {
(0, 0, true)
} else {
(line, pixel, false)
}
}
fn fill_to(&mut self, line: usize, pixel: usize) {
let last = self.beam_last;
for p in (last.line * SCREEN_WIDTH + last.pixel)..(line * SCREEN_WIDTH + pixel) {
self.buffer.set_color(
p % SCREEN_WIDTH,
p / SCREEN_WIDTH,
last.color,
ZXBrightness::Normal,
);
}
}
pub fn new_frame(&mut self) {
if !self.border_changed {
self.beam_last.reset();
}
if !self.beam_block {
self.fill_to(SCREEN_HEIGHT - 1, SCREEN_WIDTH);
}
self.beam_last.reset();
self.border_changed = false;
self.beam_block = false;
}
pub fn set_border(&mut self, clocks: usize, color: ZXColor) {
self.border_changed = true;
let (line, pixel, frame_end) = self.next_border_pixel(clocks);
if !self.beam_block {
if frame_end {
self.fill_to(SCREEN_HEIGHT - 1, SCREEN_WIDTH);
self.beam_block = true;
}
self.fill_to(line, pixel);
}
self.beam_last = BeamInfo::new(line, pixel, color);
}
pub fn frame_buffer(&self) -> &FB {
&self.buffer
}
}