use crate::console::drawing;
use crate::console::graphics::{RasterInfo, RasterOps};
use crate::console::{CharsXY, PixelsXY, RGB, SizeInPixels};
use crate::gfx::lcd::fonts::Font;
use crate::gfx::lcd::{AsByteSlice, Lcd, LcdSize, LcdXY, to_xy_size};
use std::convert::TryFrom;
use std::io;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod testutils;
pub struct BufferedLcd<L: Lcd> {
lcd: L,
font: &'static Font,
fb: Vec<u8>,
stride: usize,
sync: bool,
damage: Option<(LcdXY, LcdXY)>,
size_pixels: LcdSize,
size_chars: CharsXY,
draw_color: L::Pixel,
row_buffer: Vec<u8>,
}
impl<L> BufferedLcd<L>
where
L: Lcd,
{
pub fn new(lcd: L, font: &'static Font) -> Self {
let (size, stride) = lcd.info();
let fb = {
let pixels = size.width * size.height;
vec![0; pixels * stride]
};
let size_chars = CharsXY::new(
u16::try_from(size.width / font.glyph_size.width).expect("Must fit"),
u16::try_from(size.height / font.glyph_size.height).expect("Must fit"),
);
let draw_color = lcd.encode((255, 255, 255));
let row_buffer = Vec::with_capacity(size.width * stride);
Self {
lcd,
font,
fb,
stride,
sync: true,
damage: None,
size_pixels: size,
size_chars,
draw_color,
row_buffer,
}
}
fn without_sync<O>(&mut self, ops: O) -> io::Result<()>
where
O: Fn(&mut BufferedLcd<L>) -> io::Result<()>,
{
if self.sync {
let old_sync = self.sync;
self.sync = false;
let result = ops(self);
self.sync = old_sync;
if self.sync {
self.force_present_canvas()?;
}
result
} else {
ops(self)
}
}
fn clip_xy(&self, xy: PixelsXY) -> Option<LcdXY> {
fn clamp(value: i16, max: usize) -> Option<usize> {
if value < 0 {
None
} else {
let value = usize::try_from(value).expect("Positive value must fit");
if value > max { None } else { Some(value) }
}
}
let x = clamp(xy.x, self.size_pixels.width - 1);
let y = clamp(xy.y, self.size_pixels.height - 1);
match (x, y) {
(Some(x), Some(y)) => Some(LcdXY { x, y }),
_ => None,
}
}
fn clamp_xy(&self, xy: PixelsXY) -> LcdXY {
fn clamp(value: i16, max: usize) -> usize {
if value < 0 {
0
} else {
let value = usize::try_from(value).expect("Positive value must fit");
if value > max { max } else { value }
}
}
LcdXY {
x: clamp(xy.x, self.size_pixels.width - 1),
y: clamp(xy.y, self.size_pixels.height - 1),
}
}
fn clip_x2y2(&self, xy: PixelsXY, size: SizeInPixels) -> Option<LcdXY> {
fn clamp(value: i16, delta: u16, max: usize) -> Option<usize> {
let value = i32::from(value);
let delta = i32::from(delta);
let value = value + delta;
if value < 0 {
None
} else {
let value = usize::try_from(value).expect("Positive value must fit");
if value > max { Some(max) } else { Some(value) }
}
}
let x = clamp(xy.x, size.width - 1, self.size_pixels.width - 1);
let y = clamp(xy.y, size.height - 1, self.size_pixels.height - 1);
match (x, y) {
(Some(x), Some(y)) => Some(LcdXY { x, y }),
_ => None,
}
}
fn assert_xy_in_range(&mut self, xy: PixelsXY) {
if cfg!(test) {
let x = usize::try_from(xy.x).expect("x must be positive and must fit");
let y = usize::try_from(xy.y).expect("y must be positive and must fit");
debug_assert!(x < self.size_pixels.width, "x must be within the LCD width");
debug_assert!(y < self.size_pixels.height, "y must be within the LCD height");
}
}
fn assert_xy_size_in_range(&mut self, xy: PixelsXY, size: SizeInPixels) {
if cfg!(test) {
self.assert_xy_in_range(xy);
let x = xy.x as usize;
let y = xy.y as usize;
let width = usize::from(size.width);
let height = usize::from(size.height);
debug_assert!(
x + width - 1 < self.size_pixels.width,
"x + width must be within the LCD width"
);
debug_assert!(
y + height - 1 < self.size_pixels.height,
"y + height must be within the LCD height"
);
}
}
fn fb_addr(&self, x: usize, y: usize) -> usize {
debug_assert!(x < self.size_pixels.width);
debug_assert!(y < self.size_pixels.height);
((y * self.size_pixels.width) + x) * self.stride
}
fn damage(&mut self, x1y1: LcdXY, x2y2: LcdXY) {
debug_assert!(!self.sync);
debug_assert!(x2y2.x >= x1y1.x);
debug_assert!(x2y2.y >= x1y1.y);
if self.damage.is_none() {
self.damage = Some((x1y1, x2y2));
return;
}
let mut damage = self.damage.unwrap();
if damage.0.x > x1y1.x {
damage.0.x = x1y1.x;
}
if damage.0.y > x1y1.y {
damage.0.y = x1y1.y;
}
if damage.1.x < x2y2.x {
damage.1.x = x2y2.x;
}
if damage.1.y < x2y2.y {
damage.1.y = x2y2.y;
}
self.damage = Some(damage);
}
fn fill(&mut self, x1y1: LcdXY, x2y2: LcdXY) -> io::Result<()> {
let rowlen = {
let xlen = x2y2.x - x1y1.x + 1;
let rowlen = xlen * self.stride;
self.row_buffer.clear();
let color = self.draw_color.as_slice();
for _ in 0..xlen {
self.row_buffer.extend_from_slice(color);
}
debug_assert_eq!(rowlen, self.row_buffer.len());
rowlen
};
if self.sync {
let mut data = LcdSize::between(x1y1, x2y2).new_buffer(self.stride);
for y in x1y1.y..(x2y2.y + 1) {
let offset = self.fb_addr(x1y1.x, y);
self.fb[offset..offset + rowlen].copy_from_slice(&self.row_buffer);
data.extend(&self.row_buffer);
}
self.lcd.set_data(x1y1, x2y2, &data)?;
} else {
for y in x1y1.y..(x2y2.y + 1) {
let offset = self.fb_addr(x1y1.x, y);
self.fb[offset..offset + rowlen].copy_from_slice(&self.row_buffer);
}
self.damage(x1y1, x2y2);
}
Ok(())
}
fn force_present_canvas(&mut self) -> io::Result<()> {
let (x1y1, x2y2) = match self.damage {
None => return Ok(()),
Some(damage) => damage,
};
let mut data = LcdSize::between(x1y1, x2y2).new_buffer(self.stride);
for y in x1y1.y..(x2y2.y + 1) {
for x in x1y1.x..(x2y2.x + 1) {
let offset = self.fb_addr(x, y);
data.extend_from_slice(&self.fb[offset..offset + self.stride]);
}
}
debug_assert_eq!(
{
let (_xy, size) = to_xy_size(x1y1, x2y2);
size.width * size.height * self.stride
},
data.len()
);
self.lcd.set_data(x1y1, x2y2, &data)?;
self.damage = None;
Ok(())
}
fn write_char(&mut self, pos: LcdXY, ch: char) -> io::Result<()> {
let glyph = self.font.glyph(ch);
for j in 0..self.font.glyph_size.height {
for k in 0..self.font.stride {
let row = glyph[j * self.font.stride + k];
let mut mask = 0x80;
for i in 0..self.font.glyph_size.width {
let bit = row & mask;
if bit != 0 {
let x = pos.x + i + k * 8;
if x >= self.size_pixels.width {
continue;
}
let y = pos.y + j;
if y >= self.size_pixels.height {
continue;
}
let xy = LcdXY { x, y };
self.fill(xy, xy)?;
}
mask >>= 1;
}
}
}
Ok(())
}
}
impl<L> Drop for BufferedLcd<L>
where
L: Lcd,
{
fn drop(&mut self) {
self.set_draw_color((0, 0, 0));
self.clear().unwrap();
}
}
impl<L> RasterOps for BufferedLcd<L>
where
L: Lcd,
{
type ID = (Vec<u8>, SizeInPixels);
fn get_info(&self) -> RasterInfo {
RasterInfo {
size_pixels: self.size_pixels.into(),
glyph_size: self.font.glyph_size.into(),
size_chars: self.size_chars,
}
}
fn set_draw_color(&mut self, color: RGB) {
self.draw_color = self.lcd.encode(color);
}
fn clear(&mut self) -> io::Result<()> {
self.fill(
LcdXY { x: 0, y: 0 },
LcdXY { x: self.size_pixels.width - 1, y: self.size_pixels.height - 1 },
)
}
fn set_sync(&mut self, enabled: bool) {
self.sync = enabled;
}
fn present_canvas(&mut self) -> io::Result<()> {
if self.sync { Ok(()) } else { self.force_present_canvas() }
}
fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<Self::ID> {
self.assert_xy_size_in_range(xy, size);
let x1y1 = self.clip_xy(xy).expect("Internal ops must receive valid coordinates");
let x2y2 = self.clip_x2y2(xy, size).expect("Internal ops must receive valid coordinates");
let mut pixels = LcdSize::between(x1y1, x2y2).new_buffer(self.stride);
for y in x1y1.y..(x2y2.y + 1) {
for x in x1y1.x..(x2y2.x + 1) {
let offset = self.fb_addr(x, y);
pixels.extend_from_slice(&self.fb[offset..offset + self.stride]);
}
}
debug_assert_eq!(
usize::from(size.width) * usize::from(size.height) * self.stride,
pixels.len()
);
Ok((pixels, size))
}
fn put_pixels(&mut self, xy: PixelsXY, (pixels, size): &Self::ID) -> io::Result<()> {
debug_assert_eq!(
usize::from(size.width) * usize::from(size.height) * self.stride,
pixels.len()
);
self.assert_xy_in_range(xy);
let x1y1 = self.clip_xy(xy).expect("Internal ops must receive valid coordinates");
let x2y2 = self.clip_x2y2(xy, *size).expect("Internal ops must receive valid coordinates");
let mut p = 0;
for y in x1y1.y..(x2y2.y + 1) {
for x in x1y1.x..(x2y2.x + 1) {
let offset = self.fb_addr(x, y);
self.fb[offset..(offset + self.stride)]
.copy_from_slice(&pixels[p..(p + self.stride)]);
p += self.stride;
}
}
if self.sync {
self.lcd.set_data(x1y1, x2y2, pixels)?;
} else {
self.damage(x1y1, x2y2);
}
Ok(())
}
fn move_pixels(
&mut self,
x1y1: PixelsXY,
x2y2: PixelsXY,
size: SizeInPixels,
) -> io::Result<()> {
self.assert_xy_size_in_range(x1y1, size);
self.assert_xy_size_in_range(x2y2, size);
let data = self.read_pixels(x1y1, size)?;
self.without_sync(|self2| {
self2.draw_rect_filled(x1y1, size)?;
self2.put_pixels(x2y2, &data)
})?;
Ok(())
}
fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()> {
self.assert_xy_in_range(xy);
let x1y1 = self.clip_xy(xy).expect("Internal ops must receive valid coordinates");
self.without_sync(|self2| {
let mut pos = x1y1;
for ch in text.chars() {
self2.write_char(pos, ch)?;
pos.x += self2.font.glyph_size.width;
}
Ok(())
})
}
fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
self.without_sync(|self2| drawing::draw_circle(self2, center, radius))
}
fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
self.without_sync(|self2| drawing::draw_circle_filled(self2, center, radius))
}
fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
self.without_sync(|self2| drawing::draw_line(self2, x1y1, x2y2))
}
fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
let xy = self.clip_xy(xy);
match xy {
Some(xy) => self.fill(xy, xy),
None => Ok(()),
}
}
fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> {
self.without_sync(|self2| drawing::draw_rect(self2, xy, size))
}
fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> {
let x1y1 = self.clamp_xy(xy);
let x2y2 = self.clip_x2y2(xy, size);
match x2y2 {
Some(x2y2) => self.fill(x1y1, x2y2),
_ => Ok(()),
}
}
}