use crate::cpu::ADDR_MASK;
use crate::io::{IoRegisters, LewisIo};
pub const LCD_WIDTH: usize = 131;
pub const LCD_HEIGHT: usize = 64;
pub const LCD_HEIGHT_42S: usize = 16;
const LEWIS_LEFT_COLUMNS: usize = 66;
const LEWIS_ANNUNCIATORS: usize = 0x218;
const LEWIS_ANNUNCIATOR_ALL: usize = 0x210;
const ROW_NIBBLES: i64 = 34;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Lcd {
pub pixels: Vec<[bool; LCD_WIDTH]>,
}
impl Lcd {
pub fn blank() -> Self {
Self::blank_rows(LCD_HEIGHT)
}
pub fn blank_rows(rows: usize) -> Self {
Self {
pixels: vec![[false; LCD_WIDTH]; rows],
}
}
pub fn height(&self) -> usize {
self.pixels.len()
}
pub(crate) fn render_lewis(lewis: &LewisIo) -> Self {
let mut lcd = Self::blank_rows(LCD_HEIGHT_42S);
if !lewis.display_on() {
return lcd;
}
for x in 0..LCD_WIDTH {
let (k, half) = if x < LEWIS_LEFT_COLUMNS {
(x, 0)
} else {
(x - LEWIS_LEFT_COLUMNS, 2)
};
for line in 0..2 {
let byte = 4 * k + half + line;
let bits = lewis.ram_nibble(2 * byte) | (lewis.ram_nibble(2 * byte + 1) << 4);
for row in 0..8 {
lcd.pixels[8 * line + row][x] = bits >> row & 1 != 0;
}
}
}
lcd
}
pub(crate) fn render(io: &IoRegisters, peek: impl Fn(u32) -> u8) -> Self {
let mut lcd = Self::blank();
if !io.display_on() {
return lcd;
}
for (row, (addr, offset)) in lcd.pixels.iter_mut().zip(row_starts(io)) {
render_row(row, addr, offset, &peek);
}
lcd
}
pub(crate) fn row_spans(io: &IoRegisters) -> impl Iterator<Item = (u32, u32)> {
row_starts(io).map(|(addr, offset)| {
let last = ((LCD_WIDTH - 1 + offset) / 4) as u32;
(addr, addr.wrapping_add(last) & ADDR_MASK)
})
}
pub fn to_text(&self) -> String {
let mut s = String::with_capacity((LCD_WIDTH + 1) * self.pixels.len());
for row in &self.pixels {
s.extend(row.iter().map(|&on| if on { '#' } else { '.' }));
s.push('\n');
}
s
}
}
fn row_starts(io: &IoRegisters) -> impl Iterator<Item = (u32, usize)> {
let last_main = match io.line_count() {
0 | 1 => LCD_HEIGHT - 1,
n => usize::from(n).min(LCD_HEIGHT - 1),
};
let offset = usize::from(io.bit_offset());
let main_stride = ROW_NIBBLES + i64::from(io.line_offset()) + 2 * (offset as i64 / 4);
let main = std::iter::successors(Some(io.display_start()), move |&a| {
Some((((i64::from(a) + main_stride) as u32) & ADDR_MASK) & !1)
})
.take(last_main + 1)
.map(move |a| (a, offset));
let menu = std::iter::successors(Some(io.menu_start()), |&a| {
Some((a.wrapping_add(ROW_NIBBLES as u32) & ADDR_MASK) & !1)
})
.take(LCD_HEIGHT - 1 - last_main)
.map(|a| (a, 0));
main.chain(menu)
}
fn render_row(row: &mut [bool; LCD_WIDTH], addr: u32, offset: usize, peek: &impl Fn(u32) -> u8) {
for (x, px) in row.iter_mut().enumerate() {
let bit = x + offset;
let nib = peek(addr.wrapping_add((bit / 4) as u32) & ADDR_MASK);
*px = nib >> (bit % 4) & 1 != 0;
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Annunciators {
pub left_shift: bool,
pub right_shift: bool,
pub alpha: bool,
pub alert: bool,
pub busy: bool,
pub transmitting: bool,
pub updown: bool,
pub battery: bool,
pub g: bool,
pub rad: bool,
}
impl Annunciators {
pub(crate) fn from_bits(bits: u8) -> Self {
if bits & 0x80 == 0 {
return Self::default();
}
Self {
left_shift: bits & 0x01 != 0,
right_shift: bits & 0x02 != 0,
alpha: bits & 0x04 != 0,
alert: bits & 0x08 != 0,
busy: bits & 0x10 != 0,
transmitting: bits & 0x20 != 0,
..Self::default()
}
}
pub(crate) fn from_lewis(lewis: &LewisIo) -> Self {
if !lewis.display_on() {
return Self::default();
}
let all = lewis.ram_nibble(LEWIS_ANNUNCIATOR_ALL) != 0;
let lit = |i: usize| all || lewis.ram_nibble(LEWIS_ANNUNCIATORS + 8 * i) != 0;
Self {
updown: lit(0),
left_shift: lit(1),
transmitting: lit(2),
busy: lit(3),
battery: lit(4),
g: lit(5),
rad: lit(6),
..Self::default()
}
}
pub fn list(&self) -> [(&'static str, bool); 10] {
[
("leftshift", self.left_shift),
("rightshift", self.right_shift),
("alpha", self.alpha),
("alert", self.alert),
("busy", self.busy),
("transmit", self.transmitting),
("updown", self.updown),
("battery", self.battery),
("g", self.g),
("rad", self.rad),
]
}
pub fn to_line(&self) -> String {
let lit: Vec<&str> = self
.list()
.iter()
.filter(|(_, on)| *on)
.map(|(n, _)| *n)
.collect();
if lit.is_empty() {
"-".to_string()
} else {
lit.join(" ")
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Framebuffer {
pub pixels: Lcd,
pub annunciators: Annunciators,
pub contrast: u8,
}
impl Framebuffer {
pub fn to_text(&self) -> String {
self.pixels.to_text()
}
pub fn annunciator_line(&self) -> String {
self.annunciators.to_line()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn annunciators_need_aon() {
assert_eq!(Annunciators::from_bits(0x3F), Annunciators::default());
let a = Annunciators::from_bits(0x80 | 0x21);
assert!(a.left_shift && a.transmitting);
assert!(!a.right_shift && !a.alpha && !a.alert && !a.busy);
assert_eq!(a.to_line(), "leftshift transmit");
assert_eq!(Annunciators::default().to_line(), "-");
assert_eq!(
Annunciators::from_bits(0x80 | 0x40),
Annunciators::default()
);
}
}