use crate::{Buffer, BufferMut, Color};
use core::mem::size_of;
use core::ops::{Index, IndexMut};
use rgb::AsPixels;
use simple_blit::{blit_with, BlitOptions};
pub type TileId = u32;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TilesetOptions {
pub tile_size: (u32, u32),
pub offset: (u32, u32),
pub spacing: (u32, u32),
pub key_color: Option<Color>,
}
impl TilesetOptions {
#[inline]
pub const fn new(tile_width: u32, tile_height: u32) -> Self {
Self {
tile_size: (tile_width, tile_height),
offset: (0, 0),
spacing: (0, 0),
key_color: None,
}
}
#[inline]
pub const fn with_offset(mut self, offset_x: u32, offset_y: u32) -> Self {
self.offset = (offset_x, offset_y);
self
}
#[inline]
pub const fn with_margin(mut self, margin_x: u32, margin_y: u32) -> Self {
self.spacing = (margin_x, margin_y);
self
}
#[inline]
pub const fn with_key_color(mut self, key_color: Color) -> Self {
self.key_color = Some(key_color);
self
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tileset<C> {
data: C,
width: u32,
height: u32,
tile_counts: (u32, u32),
pub(crate) opts: TilesetOptions,
}
impl<C> Tileset<C> {
#[inline]
pub fn contains(&self, id: TileId) -> bool {
id < self.tile_count()
}
#[inline]
pub fn tile_count(&self) -> u32 {
self.tile_counts.0 * self.tile_counts.1
}
#[inline]
pub fn options(&self) -> &TilesetOptions {
&self.opts
}
}
impl<C> Tileset<C>
where
C: AsRef<[u8]>,
{
pub fn new(data: C, width: u32, height: u32, opts: TilesetOptions) -> Option<Self> {
if data.as_ref().len() == ((width * height) as usize * size_of::<C>()) {
let tile_counts = calc_tile_counts(width, height, &opts);
Some(Self {
data,
width,
height,
tile_counts,
opts,
})
} else {
None
}
}
pub fn get_tile_pos(&self, id: TileId) -> Option<(u32, u32)> {
let x = (id % self.tile_counts.0) * (self.opts.tile_size.0 + self.opts.spacing.0)
+ self.opts.offset.0;
let y = (id / self.tile_counts.0) * (self.opts.tile_size.1 + self.opts.spacing.1)
+ self.opts.offset.1;
if (x + self.opts.tile_size.0) < self.width as _
&& (y + self.opts.tile_size.1) < self.height as _
{
Some((x, y))
} else {
None
}
}
pub fn render_tile(
&self,
surface: &mut (impl BufferMut<Color> + ?Sized),
id: TileId,
offset_x: i32,
offset_y: i32,
opts: BlitOptions,
) {
if let Some((x, y)) = self.get_tile_pos(id) {
blit_with(
surface,
(offset_x, offset_y),
self,
(x as _, y as _),
self.opts.tile_size,
opts,
|dest, src, _| {
if Some(*src) != self.opts.key_color {
*dest = *src;
}
},
)
}
}
}
impl<C> Buffer<Color> for Tileset<C>
where
C: AsRef<[u8]>,
{
#[inline]
fn width(&self) -> u32 {
self.width
}
#[inline]
fn height(&self) -> u32 {
self.height
}
#[inline]
fn get(&self, x: u32, y: u32) -> &Color {
self.data
.as_ref()
.as_pixels()
.index((y * self.width + x) as usize)
}
}
impl<C> BufferMut<Color> for Tileset<C>
where
C: AsRef<[u8]> + AsMut<[u8]>,
{
#[inline]
fn get_mut(&mut self, x: u32, y: u32) -> &mut Color {
self.data
.as_mut()
.as_pixels_mut()
.index_mut((y * self.width + x) as usize)
}
}
#[inline]
const fn calc_tile_counts(width: u32, height: u32, opts: &TilesetOptions) -> (u32, u32) {
(
(width - opts.offset.0 + opts.spacing.0) / (opts.tile_size.0 + opts.spacing.0),
(height - opts.offset.1 + opts.spacing.1) / (opts.tile_size.1 + opts.spacing.1),
)
}