cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
use bitflags::bitflags;
use std::fmt;
use std::fmt::Formatter;

bitflags! {
    #[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
    pub struct EmphasisFlags: u16 {
        const EMPHASIZE_RED =           0b00000000_01000000;
        const EMPHASIZE_GREEN =         0b00000000_10000000;
        const EMPHASIZE_BLUE =          0b00000001_00000000;
    }
}

const EMPHASIS_MASK: u16 = 0b00000001_11000000; // 3 bits for emphasis (red, green, blue)
const HUE_MASK: u8 = 0b00001111; // 4 bits for hue (0-15)
const LUMA_MASK: u8 = 0b00110000; // 2 bits for luma (0-3)
const COLOR_INDEX_MASK: u8 = 0b00111111; // 6 bits total for color (hue + luma)

/// Represents a NES palette color including color emphasis bits.
///
/// Internally, this is a packed 8-bit value, with the lower 4 bits as hue and the upper 2 bits as luma.
/// The NES supports 64 colors, but only 54 are distinct.
#[derive(Default, PartialEq, Eq, Clone, Copy)]
pub struct Color {
    /// The packed color value, using the lower 9 bits.
    ///
    /// Bits 0-3 represent the hue (0-15)
    /// Bits 4-5 represent the luma (0-3).
    /// Bits 6-8 represent emphasis flags.
    value: u16,
}

impl From<Color> for (u8, EmphasisFlags) {
    fn from(value: Color) -> Self {
        (value.color_index(), value.emphasis())
    }
}

impl From<(u8, EmphasisFlags)> for Color {
    fn from(value: (u8, EmphasisFlags)) -> Self {
        let color_index = (value.0 & COLOR_INDEX_MASK) as u16;
        let emphasis = value.1.bits() & EMPHASIS_MASK;
        let value = color_index | emphasis;
        Self { value }
    }
}

impl fmt::Debug for Color {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Color({}), hue:{} luma:{}",
            self.value,
            self.hue(),
            self.luma()
        )?;
        if self.emphasis() != EmphasisFlags::empty() {
            write!(
                f,
                " Emphasis: r:{}, g:{}, b:{}",
                self.emphasis().contains(EmphasisFlags::EMPHASIZE_RED),
                self.emphasis().contains(EmphasisFlags::EMPHASIZE_GREEN),
                self.emphasis().contains(EmphasisFlags::EMPHASIZE_BLUE)
            )?;
        }
        Ok(())
    }
}

impl Color {
    /// Creates a new `Color` from a raw 8-bit color and emphasis flags.
    pub fn new(value: u8, emphasis: EmphasisFlags) -> Self {
        (value, emphasis).into()
    }

    /// Returns the hue component (0-15) of the color.
    pub fn hue(&self) -> u8 {
        self.value as u8 & HUE_MASK
    }

    /// Returns the luma (brightness) component (0-3) of the color.
    pub fn luma(&self) -> u8 {
        (self.value as u8 & LUMA_MASK) >> 4
    }

    /// Returns the color index (0-63) of the color.
    ///
    /// This is a combination of hue and luma, but does not include emphasis.
    /// The color index is calculated as `hue + (luma << 4)`.
    pub fn color_index(&self) -> u8 {
        self.value as u8 & COLOR_INDEX_MASK
    }

    /// Returns the emphasis flags for this color.
    ///
    /// This includes the 3 bits for red, green, and blue emphasis.
    pub fn emphasis(&self) -> EmphasisFlags {
        EmphasisFlags::from_bits_truncate(self.value & EMPHASIS_MASK)
    }

    /// Returns the internal value, packing hue, luma, and emphasis.
    pub fn value(&self) -> u16 {
        self.value
    }

    /// Sets the hue component (0-15) of the color.
    ///
    /// # Panics
    /// Panics if `hue >= 16`.
    pub fn set_hue(&mut self, hue: u8) {
        debug_assert!(hue < 16);
        let not_hue_bits = self.value & !(HUE_MASK as u16);
        let hue_bits = (hue & HUE_MASK) as u16;
        self.value = not_hue_bits | hue_bits;
    }

    /// Sets the luma (brightness) component (0-3) of the color.
    ///
    /// # Panics
    /// Panics if `luma >= 4`.
    pub fn set_luma(&mut self, luma: u8) {
        debug_assert!(luma < 4);
        let not_luma_bits = self.value & !(LUMA_MASK as u16);
        let luma_bits = (luma as u16) << 4;
        self.value = not_luma_bits | luma_bits;
    }
}