use core::alloc::Layout;
use crate::display::palette16::Palette16;
use super::{BYTES_PER_TILE_4BPP, BYTES_PER_TILE_8BPP};
pub struct Sprite {
pub(crate) palette: Palette,
pub(crate) data: &'static [u8],
pub(crate) size: Size,
}
#[derive(Clone, Copy)]
pub enum Palette {
Single(&'static Palette16),
Multi(&'static PaletteMulti),
}
impl Palette {
pub(crate) fn is_multi(self) -> bool {
matches!(self, Palette::Multi(_))
}
}
pub struct PaletteMulti {
first_index: u32,
palettes: &'static [Palette16],
}
impl PaletteMulti {
#[must_use]
pub const fn new(palettes: &'static [Palette16]) -> Self {
assert!(palettes.len() <= 16);
assert!(!palettes.is_empty());
Self {
first_index: (16 - palettes.len()) as u32,
palettes,
}
}
#[must_use]
pub const fn palettes(&self) -> &'static [Palette16] {
self.palettes
}
#[must_use]
pub const fn first_index(&self) -> u32 {
self.first_index
}
#[must_use]
pub const fn first_colour_index(&self) -> u8 {
(self.first_index * 16) as u8
}
}
impl Sprite {
#[doc(hidden)]
#[must_use]
pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self {
Self {
palette: Palette::Single(palette),
data,
size,
}
}
#[doc(hidden)]
#[must_use]
pub const unsafe fn new_multi(
palettes: &'static PaletteMulti,
data: &'static [u8],
size: Size,
) -> Self {
Self {
palette: Palette::Multi(palettes),
data,
size,
}
}
#[must_use]
pub fn size(&self) -> Size {
self.size
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[allow(missing_docs)]
pub enum Size {
S8x8 = 0b00_00,
S16x16 = 0b00_01,
S32x32 = 0b00_10,
S64x64 = 0b00_11,
S16x8 = 0b01_00,
S32x8 = 0b01_01,
S32x16 = 0b01_10,
S64x32 = 0b01_11,
S8x16 = 0b10_00,
S8x32 = 0b10_01,
S16x32 = 0b10_10,
S32x64 = 0b10_11,
}
#[doc(hidden)]
#[macro_export]
macro_rules! align_bytes {
($align_ty:ty, $data:literal) => {{
#[repr(C)] struct AlignedAs<Align, Bytes: ?Sized> {
pub _align: [Align; 0],
pub bytes: Bytes,
}
const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs {
_align: [],
bytes: *$data,
};
&ALIGNED.bytes
}};
}
#[macro_export]
macro_rules! include_aseprite {
($v: vis mod $module: ident, $($aseprite_args: tt)*) => {
$v mod $module {
#[allow(unused_imports)]
use $crate::display::object::{Size, Sprite, Tag};
use $crate::display::{Palette16, Rgb15};
use $crate::align_bytes;
$crate::include_aseprite_inner!($($aseprite_args)*);
}
};
}
#[macro_export]
macro_rules! include_aseprite_256 {
($v: vis mod $module: ident, $($aseprite_args: tt)*) => {
$v mod $module {
#[allow(unused_imports)]
use $crate::display::object::{Size, Sprite, Tag, PaletteMulti};
use $crate::display::{Palette16, Rgb15};
use $crate::align_bytes;
$crate::include_aseprite_256_inner!($($aseprite_args)*);
}
}
}
pub use include_aseprite;
#[derive(Clone, Copy)]
enum Direction {
Forward,
Backward,
PingPong,
}
impl Direction {
const fn from_usize(a: usize) -> Self {
match a {
0 => Direction::Forward,
1 => Direction::Backward,
2 => Direction::PingPong,
_ => panic!("Invalid direction, this is a bug in image converter or agb"),
}
}
}
pub struct Tag {
sprites: &'static [Sprite],
direction: Direction,
}
unsafe impl Sync for Tag {}
impl Tag {
#[must_use]
pub fn sprites(&self) -> &'static [Sprite] {
self.sprites
}
#[must_use]
pub const fn sprite(&self, idx: usize) -> &'static Sprite {
&self.sprites[idx]
}
#[inline]
#[must_use]
pub fn animation_sprite(&self, idx: usize) -> &'static Sprite {
let len_sub_1 = self.sprites.len() - 1;
match self.direction {
Direction::Forward => self.sprite(idx % self.sprites.len()),
Direction::Backward => self.sprite(len_sub_1 - (idx % self.sprites.len())),
Direction::PingPong => self.sprite(
(((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize)
.unsigned_abs(),
),
}
}
pub fn animation_frame(&self, idx: &mut usize, divider: u32) -> &'static Sprite {
let divided = *idx >> divider;
let idx = match self.direction {
Direction::Forward => {
if divided >= self.sprites.len() {
*idx = 0;
0
} else {
divided
}
}
Direction::Backward => {
if divided >= self.sprites.len() {
*idx = 0;
self.sprites.len() - 1
} else {
self.sprites.len() - 1 - divided
}
}
Direction::PingPong => {
if divided >= (self.sprites.len() - 1) * 2 {
*idx = 0;
0
} else if divided >= self.sprites.len() {
(self.sprites.len() - 1) * 2 - divided
} else {
divided
}
}
};
&self.sprites[idx]
}
#[doc(hidden)]
#[must_use]
pub const fn new(sprites: &'static [Sprite], direction: usize) -> Self {
Self {
sprites,
direction: Direction::from_usize(direction),
}
}
}
impl Size {
pub(crate) const fn number_of_tiles(self) -> usize {
match self {
Size::S8x8 => 1,
Size::S16x16 => 4,
Size::S32x32 => 16,
Size::S64x64 => 64,
Size::S16x8 => 2,
Size::S32x8 => 4,
Size::S32x16 => 8,
Size::S64x32 => 32,
Size::S8x16 => 2,
Size::S8x32 => 4,
Size::S16x32 => 8,
Size::S32x64 => 32,
}
}
pub(crate) const fn shape_size(self) -> (u16, u16) {
(self as u16 >> 2, self as u16 & 0b11)
}
pub(crate) fn layout(self, multi_palette: bool) -> Layout {
Layout::from_size_align(
self.number_of_tiles() * BYTES_PER_TILE_4BPP * (multi_palette as usize + 1),
8,
)
.unwrap()
}
#[must_use]
pub fn size_bytes_16(self) -> usize {
self.number_of_tiles() * BYTES_PER_TILE_4BPP
}
#[must_use]
pub fn size_bytes_256(self) -> usize {
self.number_of_tiles() * BYTES_PER_TILE_8BPP
}
#[must_use]
pub const fn from_width_height(width: usize, height: usize) -> Self {
match (width, height) {
(8, 8) => Size::S8x8,
(16, 16) => Size::S16x16,
(32, 32) => Size::S32x32,
(64, 64) => Size::S64x64,
(16, 8) => Size::S16x8,
(32, 8) => Size::S32x8,
(32, 16) => Size::S32x16,
(64, 32) => Size::S64x32,
(8, 16) => Size::S8x16,
(8, 32) => Size::S8x32,
(16, 32) => Size::S16x32,
(32, 64) => Size::S32x64,
(_, _) => panic!("Bad width and height!"),
}
}
#[must_use]
pub const fn to_width_height(self) -> (usize, usize) {
match self {
Size::S8x8 => (8, 8),
Size::S16x16 => (16, 16),
Size::S32x32 => (32, 32),
Size::S64x64 => (64, 64),
Size::S16x8 => (16, 8),
Size::S32x8 => (32, 8),
Size::S32x16 => (32, 16),
Size::S64x32 => (64, 32),
Size::S8x16 => (8, 16),
Size::S8x32 => (8, 32),
Size::S16x32 => (16, 32),
Size::S32x64 => (32, 64),
}
}
#[must_use]
pub const fn to_tiles_width_height(self) -> (usize, usize) {
let wh = self.to_width_height();
(wh.0 / 8, wh.1 / 8)
}
}