use core::{alloc::Layout, slice};
use crate::display::palette16::Palette16;
use super::BYTES_PER_TILE_4BPP;
pub struct Sprite {
pub(crate) palette: &'static Palette16,
pub(crate) data: &'static [u8],
pub(crate) size: Size,
}
impl Sprite {
#[doc(hidden)]
#[must_use]
pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self {
Self {
palette,
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 {
($($aseprite_path: expr),*) => {{
use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics};
use $crate::display::palette16::Palette16;
use $crate::align_bytes;
$crate::include_aseprite_inner!($($aseprite_path),*);
&Graphics::new(SPRITES, TAGS)
}};
}
pub use include_aseprite;
pub struct Graphics {
sprites: &'static [Sprite],
tag_map: &'static TagMap,
}
impl Graphics {
#[doc(hidden)]
#[must_use]
pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self {
Self { sprites, tag_map }
}
#[must_use]
pub const fn tags(&self) -> &TagMap {
self.tag_map
}
#[must_use]
pub const fn sprites(&self) -> &[Sprite] {
self.sprites
}
}
pub struct TagMap {
tags: &'static [(&'static str, Tag)],
}
const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
impl TagMap {
#[doc(hidden)]
#[must_use]
pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap {
Self { tags }
}
#[doc(hidden)]
#[must_use]
pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> {
let mut i = 0;
while i < self.tags.len() {
let s = self.tags[i].0;
if const_byte_compare(s.as_bytes(), tag.as_bytes()) {
return Some(&self.tags[i].1);
}
i += 1;
}
None
}
#[must_use]
pub const fn get(&'static self, tag: &str) -> &'static Tag {
let t = self.try_get(tag);
match t {
Some(t) => t,
None => panic!("The requested tag does not exist"),
}
}
pub fn values(&self) -> impl Iterator<Item = &'static Tag> {
self.tags.iter().map(|x| &x.1)
}
}
#[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: *const Sprite,
len: usize,
direction: Direction,
}
impl Tag {
#[must_use]
pub fn sprites(&self) -> &'static [Sprite] {
unsafe { slice::from_raw_parts(self.sprites, self.len) }
}
#[must_use]
pub const fn sprite(&self, idx: usize) -> &'static Sprite {
if idx >= self.len {
panic!("out of bounds access to sprite");
}
unsafe { &*self.sprites.add(idx) }
}
#[inline]
#[must_use]
pub fn animation_sprite(&self, idx: usize) -> &'static Sprite {
let len_sub_1 = self.len - 1;
match self.direction {
Direction::Forward => self.sprite(idx % self.len),
Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)),
Direction::PingPong => self.sprite(
(((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize)
.unsigned_abs(),
),
}
}
#[doc(hidden)]
#[must_use]
pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self {
assert!(from <= to);
assert!(to < sprites.len());
Self {
sprites: &sprites[from] as *const Sprite,
len: to - from + 1,
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) -> Layout {
Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap()
}
#[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),
}
}
}