1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright 2024 Mikael Lund aka Wombat
//
// Licensed under the Apache license, version 2.0 (the "license");
// you may not use this file except in compliance with the license.
// You may obtain a copy of the license at
//
//     http://www.apache.org/licenses/license-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the license is distributed on an "as is" basis,
// without warranties or conditions of any kind, either express or implied.
// See the license for the specific language governing permissions and
// limitations under the license.

//! Commodore 64 display drivers and color palette

use embedded_graphics_core::pixelcolor::*;
use embedded_graphics_core::prelude::*;

/// Color palette of the VIC-II chip found in e.g. the Commodore 64 and 128
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum VicIIPalette {
    Black = 0,
    White = 1,
    Red = 2,
    Cyan = 3,
    Purple = 4,
    Green = 5,
    Blue = 6,
    Yellow = 7,
    Orange = 8,
    Brown = 9,
    LightRed = 10,
    /// Also known as gray 1
    DarkGray = 11,
    /// Also known as gray 2
    Gray = 12,
    LightGreen = 13,
    LightBlue = 14,
    /// Also known as Gray 3
    LightGray = 15,
}

impl const From<Gray8> for VicIIPalette {
    fn from(value: Gray8) -> Self {
        match value.luma() {
            0..=31 => Self::Black,
            32..=63 => Self::DarkGray,
            64..=95 => Self::Gray,
            96..=127 => Self::LightGray,
            128..=255 => Self::White,
        }
    }
}

/// Todo: currently this simply transforms to grey scale
impl const From<Rgb555> for VicIIPalette {
    fn from(value: Rgb555) -> Self {
        Gray8::from(value).into()
    }
}

/// Todo: currently this simply transforms to grey scale
impl const From<Rgb888> for VicIIPalette {
    fn from(value: Rgb888) -> Self {
        Gray8::from(value).into()
    }
}

impl const From<VicIIPalette> for Rgb888 {
    fn from(color: VicIIPalette) -> Self {
        match color {
            VicIIPalette::Black => Self::new(0, 0, 0),
            VicIIPalette::White => Self::new(255, 255, 255),
            VicIIPalette::Red => Self::new(136, 0, 0),
            VicIIPalette::Cyan => Self::new(170, 255, 238),
            VicIIPalette::Purple => Self::new(204, 68, 204),
            VicIIPalette::Green => Self::new(0, 204, 85),
            VicIIPalette::Blue => Self::new(0, 0, 170),
            VicIIPalette::Yellow => Self::new(238, 238, 119),
            VicIIPalette::Orange => Self::new(221, 136, 85),
            VicIIPalette::Brown => Self::new(102, 68, 0),
            VicIIPalette::LightRed => Self::new(255, 119, 119),
            VicIIPalette::DarkGray => Self::new(51, 51, 51),
            VicIIPalette::Gray => Self::new(119, 119, 119),
            VicIIPalette::LightGreen => Self::new(170, 255, 102),
            VicIIPalette::LightBlue => Self::new(0, 136, 255),
            VicIIPalette::LightGray => Self::new(187, 187, 187),
        }
    }
}

impl const PixelColor for VicIIPalette {
    type Raw = ();
}

/// Color PETSCII Display Driver
///
/// Simple display driver for the C64's PETSCII text mode.
/// A "pixel" is a colored, filled square character on the 40 x 25 text display.
///
/// # Examples
/// ~~~
/// use retro_display::c64::{C64Color, PetsciiDisplay};
/// let mut display = PetsciiDisplay {};
/// display.clear(C64Color::Blue)?;
/// ~~~
pub struct PetsciiDisplay {}

impl PetsciiDisplay {
    /// Number of columns in the C64 PETSCII display
    const COLS: isize = 40;
    /// Number of rows in the C64 PETSCII display
    const ROWS: isize = 25;
    /// VIC-II video memory pointer
    const VIDEO_RAM: *mut u8 = (0x0400) as *mut u8;
    /// VIC-II color memory pointer
    const COLOR_RAM: *mut u8 = (0xd800) as *mut u8;
    /// PETSCII symbol to mimic a pixel (filled square)
    const PIXEL_SYMBOL: u8 = 0xa0;

    /// Set pixel without checking if the position is within bounds
    ///
    /// # Safety
    ///
    /// Unsafe as the index is unchecked and may write to memory outside the display.
    ///
    unsafe fn set_pixel_unchecked(index: isize, color: VicIIPalette) {
        Self::COLOR_RAM.offset(index).write(color as u8);
        Self::VIDEO_RAM.offset(index).write(Self::PIXEL_SYMBOL);
    }

    /// Set pixel with bounds checking
    fn set_pixel(coord: &Point, color: VicIIPalette) {
        // inelegant but small(est?) binary size
        let x = coord.x as isize;
        if (0..Self::COLS).contains(&x) {
            let y = coord.y as isize;
            if (0..Self::ROWS).contains(&y) {
                let index = x + y * Self::COLS;
                unsafe {
                    Self::set_pixel_unchecked(index, color);
                }
            }
        }
    }
}

impl const OriginDimensions for PetsciiDisplay {
    fn size(&self) -> Size {
        Size::new(Self::COLS as u32, Self::ROWS as u32)
    }
}

impl DrawTarget for PetsciiDisplay {
    type Color = VicIIPalette;
    type Error = core::convert::Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        pixels
            .into_iter()
            .for_each(|Pixel(coord, color)| Self::set_pixel(&coord, color));
        Ok(())
    }
}