use core::cell::RefCell;
use alloc::boxed::Box;
use alloc::vec::Vec;
use playdate_rs_sys::LCDPattern;
use crate::{
graphics::Bitmap,
math::{Rect, SideOffsets, Vec2},
util::Ref,
PLAYDATE,
};
pub use sys::SpriteCollisionResponseType;
pub struct PlaydateSprite {
handle: *const sys::playdate_sprite,
}
impl PlaydateSprite {
pub(crate) fn new(handle: *const sys::playdate_sprite) -> Self {
Self { handle }
}
pub fn set_always_redraw(&self, flag: bool) {
unsafe {
(*self.handle).setAlwaysRedraw.unwrap()(flag as i32);
}
}
pub fn add_dirty_rect(&self, dirty_rect: SideOffsets<i32>) {
unsafe {
(*self.handle).addDirtyRect.unwrap()(dirty_rect.into());
}
}
pub fn draw_sprites(&self) {
unsafe {
(*self.handle).drawSprites.unwrap()();
}
}
pub fn update_and_draw_sprites(&self) {
unsafe {
(*self.handle).updateAndDrawSprites.unwrap()();
}
}
pub fn add_sprite(&self, sprite: impl AsRef<Sprite>) {
unsafe {
(*self.handle).addSprite.unwrap()(sprite.as_ref().handle);
}
}
pub fn remove_sprite(&self, sprite: impl AsRef<Sprite>) {
unsafe {
(*self.handle).removeSprite.unwrap()(sprite.as_ref().handle);
}
}
pub fn remove_sprites(&self, sprites: &[&Sprite]) {
let mut sprites: Vec<_> = sprites.iter().map(|s| s.handle).collect();
unsafe {
(*self.handle).removeSprites.unwrap()(sprites.as_mut_ptr(), sprites.len() as i32);
}
}
pub fn remove_all_sprites(&self) {
unsafe {
(*self.handle).removeAllSprites.unwrap()();
}
}
pub fn get_sprite_count(&self) -> usize {
unsafe { (*self.handle).getSpriteCount.unwrap()() as _ }
}
pub fn set_clip_rects_in_range(&self, clip_rect: SideOffsets<i32>, start_z: i32, end_z: i32) {
unsafe {
(*self.handle).setClipRectsInRange.unwrap()(clip_rect.into(), start_z, end_z);
}
}
pub fn clear_clip_rects_in_range(&self, start_z: i32, end_z: i32) {
unsafe {
(*self.handle).clearClipRectsInRange.unwrap()(start_z, end_z);
}
}
pub fn reset_collision_world(&self) {
unsafe {
(*self.handle).resetCollisionWorld.unwrap()();
}
}
pub fn query_sprites_at_point(&self, pos: Vec2<f32>) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites =
unsafe { (*self.handle).querySpritesAtPoint.unwrap()(pos.x, pos.y, &mut len) };
let mut result = Vec::new();
for i in 0..len {
let sprite = unsafe { sprites.offset(i as isize).as_ref().unwrap() };
result.push(Sprite::from_ref(*sprite));
}
PLAYDATE.system.realloc(sprites as _, 0);
result
}
pub fn query_sprites_in_rect(&self, rect: Rect<f32>) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites = unsafe {
(*self.handle).querySpritesInRect.unwrap()(
rect.x,
rect.y,
rect.width,
rect.height,
&mut len,
)
};
let mut result = Vec::new();
for i in 0..len {
let sprite = unsafe { sprites.offset(i as isize).as_ref().unwrap() };
result.push(Sprite::from_ref(*sprite));
}
PLAYDATE.system.realloc(sprites as _, 0);
result
}
pub fn query_sprites_along_line(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites =
unsafe { (*self.handle).querySpritesAlongLine.unwrap()(x1, y1, x2, y2, &mut len) };
let mut result = Vec::new();
for i in 0..len {
let sprite = unsafe { sprites.offset(i as isize).as_ref().unwrap() };
result.push(Sprite::from_ref(*sprite));
}
PLAYDATE.system.realloc(sprites as _, 0);
result
}
pub fn query_sprite_info_along_line(
&self,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
) -> Vec<SpriteQueryInfo> {
let mut len = 0;
let info =
unsafe { (*self.handle).querySpriteInfoAlongLine.unwrap()(x1, y1, x2, y2, &mut len) };
let mut result = Vec::new();
for i in 0..len {
let info = unsafe { info.offset(i as isize).as_ref().unwrap() };
result.push(SpriteQueryInfo::new(info));
}
PLAYDATE.system.realloc(info as _, 0);
result
}
pub fn all_overlapping_sprites(&self) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites = unsafe { (*self.handle).allOverlappingSprites.unwrap()(&mut len) };
let mut result = Vec::new();
for i in 0..len {
let sprite = unsafe { sprites.offset(i as isize).as_ref().unwrap() };
result.push(Sprite::from_ref(*sprite));
}
PLAYDATE.system.realloc(sprites as _, 0);
result
}
}
#[derive(Debug)]
pub struct Sprite {
handle: *mut sys::LCDSprite,
}
impl PartialEq for Sprite {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Eq for Sprite {}
unsafe impl Sync for Sprite {}
unsafe impl Send for Sprite {}
impl Default for Sprite {
fn default() -> Self {
Self::new()
}
}
type DataCell<F> = RefCell<Option<F>>;
type UpdateFn = Box<dyn Fn(&Sprite)>;
type DrawFn = Box<dyn Fn(&Sprite, Rect<f32>, Rect<f32>)>;
type CollisionResponseFn = Box<dyn Fn(&Sprite, &Sprite) -> SpriteCollisionResponseType>;
#[derive(Default)]
struct SpriteData {
update_fn: DataCell<UpdateFn>,
draw_fn: DataCell<DrawFn>,
collision_response_fn: DataCell<CollisionResponseFn>,
}
impl Sprite {
pub(crate) fn from(handle: *mut sys::LCDSprite) -> Self {
Self { handle }
}
pub(crate) fn from_ref<'a>(handle: *mut sys::LCDSprite) -> Ref<'a, Self> {
Ref::new(Self { handle })
}
pub fn new() -> Self {
Self::from(unsafe { (*PLAYDATE.sprite.handle).newSprite.unwrap()() })
}
pub fn get_position(&self) -> Vec2<f32> {
let mut x = 0.0;
let mut y = 0.0;
unsafe {
(*PLAYDATE.sprite.handle).getPosition.unwrap()(self.handle, &mut x, &mut y);
}
Vec2::new(x, y)
}
pub fn set_bounds(&self, bounds: Rect<f32>) {
unsafe { (*PLAYDATE.sprite.handle).setBounds.unwrap()(self.handle, bounds.into()) }
}
pub fn get_bounds(&self) -> Rect<f32> {
unsafe { (*PLAYDATE.sprite.handle).getBounds.unwrap()(self.handle).into() }
}
pub fn move_to(&self, pos: Vec2<f32>) {
unsafe { (*PLAYDATE.sprite.handle).moveTo.unwrap()(self.handle, pos.x, pos.y) }
}
pub fn move_by(&self, delta: Vec2<f32>) {
unsafe { (*PLAYDATE.sprite.handle).moveBy.unwrap()(self.handle, delta.x, delta.y) }
}
pub fn set_image(&self, image: Bitmap, flip: sys::LCDBitmapFlip) {
if let Some(old_image) = self.get_image() {
let _boxed = Bitmap::from(old_image.handle);
}
unsafe {
(*PLAYDATE.sprite.handle).setImage.unwrap()(self.handle, image.as_ref().handle, flip)
};
core::mem::forget(image);
}
pub fn get_image(&self) -> Option<Ref<Bitmap>> {
let ptr = unsafe { (*PLAYDATE.sprite.handle).getImage.unwrap()(self.handle) };
if ptr.is_null() {
None
} else {
Some(Bitmap::from_ref(ptr))
}
}
pub fn set_size(&self, width: f32, height: f32) {
unsafe { (*PLAYDATE.sprite.handle).setSize.unwrap()(self.handle, width, height) }
}
pub fn set_z_index(&self, z_index: i16) {
unsafe { (*PLAYDATE.sprite.handle).setZIndex.unwrap()(self.handle, z_index) }
}
pub fn get_z_index(&self) -> i16 {
unsafe { (*PLAYDATE.sprite.handle).getZIndex.unwrap()(self.handle) }
}
pub fn set_draw_mode(&self, mode: sys::LCDBitmapDrawMode) {
unsafe { (*PLAYDATE.sprite.handle).setDrawMode.unwrap()(self.handle, mode) }
}
pub fn set_image_flip(&self, flip: sys::LCDBitmapFlip) {
unsafe { (*PLAYDATE.sprite.handle).setImageFlip.unwrap()(self.handle, flip) }
}
pub fn get_image_flip(&self) -> sys::LCDBitmapFlip {
unsafe { (*PLAYDATE.sprite.handle).getImageFlip.unwrap()(self.handle) }
}
pub fn set_stencil(&self, stencil: impl AsRef<Bitmap>) {
unsafe {
(*PLAYDATE.sprite.handle).setStencil.unwrap()(self.handle, stencil.as_ref().handle)
};
}
pub fn set_clip_rect(&self, clip_rect: SideOffsets<i32>) {
unsafe { (*PLAYDATE.sprite.handle).setClipRect.unwrap()(self.handle, clip_rect.into()) };
}
pub fn clear_clip_rect(&self) {
unsafe { (*PLAYDATE.sprite.handle).clearClipRect.unwrap()(self.handle) };
}
pub fn set_updates_enabled(&self, flag: bool) {
unsafe { (*PLAYDATE.sprite.handle).setUpdatesEnabled.unwrap()(self.handle, flag as _) };
}
pub fn updates_enabled(&self) -> bool {
unsafe { (*PLAYDATE.sprite.handle).updatesEnabled.unwrap()(self.handle) == 1 }
}
pub fn set_collisions_enabled(&self, flag: bool) {
unsafe { (*PLAYDATE.sprite.handle).setCollisionsEnabled.unwrap()(self.handle, flag as _) };
}
pub fn collisions_enabled(&self) -> bool {
unsafe { (*PLAYDATE.sprite.handle).collisionsEnabled.unwrap()(self.handle) == 1 }
}
pub fn set_visible(&self, flag: bool) {
unsafe { (*PLAYDATE.sprite.handle).setVisible.unwrap()(self.handle, flag as _) };
}
pub fn is_visible(&self) -> bool {
unsafe { (*PLAYDATE.sprite.handle).isVisible.unwrap()(self.handle) == 1 }
}
pub fn set_opaque(&self, flag: bool) {
unsafe { (*PLAYDATE.sprite.handle).setOpaque.unwrap()(self.handle, flag as _) };
}
pub fn mark_dirty(&self) {
unsafe { (*PLAYDATE.sprite.handle).markDirty.unwrap()(self.handle) };
}
pub fn set_tag(&self, tag: u8) {
unsafe { (*PLAYDATE.sprite.handle).setTag.unwrap()(self.handle, tag) }
}
pub fn get_tag(&self) -> u8 {
unsafe { (*PLAYDATE.sprite.handle).getTag.unwrap()(self.handle) }
}
pub fn set_ignores_draw_offset(&self, flag: i32) {
unsafe { (*PLAYDATE.sprite.handle).setIgnoresDrawOffset.unwrap()(self.handle, flag) };
}
pub fn set_update_function(&self, func: impl Fn(&Sprite) + 'static) {
*self.get_userdata().update_fn.borrow_mut() = Some(Box::new(func));
extern "C" fn callback(sprite: *mut sys::LCDSprite) {
let sprite = Sprite::from_ref(sprite);
let func = sprite.get_userdata().update_fn.borrow();
let func = &func.as_ref().unwrap();
func(&sprite)
}
unsafe {
(*PLAYDATE.sprite.handle).setUpdateFunction.unwrap()(self.handle, Some(callback))
};
}
pub fn set_draw_function(&self, func: impl Fn(&Sprite, Rect<f32>, Rect<f32>) + 'static) {
*self.get_userdata().draw_fn.borrow_mut() = Some(Box::new(func));
extern "C" fn callback(
sprite: *mut sys::LCDSprite,
bounds: sys::PDRect,
drawrect: sys::PDRect,
) {
let sprite = Sprite::from_ref(sprite);
let func = sprite.get_userdata().draw_fn.borrow();
let func = &func.as_ref().unwrap();
func(&sprite, bounds.into(), drawrect.into())
}
unsafe { (*PLAYDATE.sprite.handle).setDrawFunction.unwrap()(self.handle, Some(callback)) };
}
pub fn set_collide_rect(&self, collide_rect: Rect<f32>) {
unsafe {
(*PLAYDATE.sprite.handle).setCollideRect.unwrap()(self.handle, collide_rect.into());
}
}
pub fn get_collide_rect(&self) -> Rect<f32> {
unsafe { (*PLAYDATE.sprite.handle).getCollideRect.unwrap()(self.handle).into() }
}
pub fn clear_collide_rect(&self) {
unsafe { (*PLAYDATE.sprite.handle).clearCollideRect.unwrap()(self.handle) };
}
pub fn set_collision_response_function(
&self,
func: impl Fn(&Sprite, &Sprite) -> SpriteCollisionResponseType + 'static,
) {
*self.get_userdata().collision_response_fn.borrow_mut() = Some(Box::new(func));
extern "C" fn callback(
sprite: *mut sys::LCDSprite,
other: *mut sys::LCDSprite,
) -> SpriteCollisionResponseType {
let sprite = Sprite::from_ref(sprite);
let other = Sprite::from_ref(other);
let func = sprite.get_userdata().collision_response_fn.borrow();
let func = &func.as_ref().unwrap();
func(&sprite, &other)
}
unsafe {
(*PLAYDATE.sprite.handle)
.setCollisionResponseFunction
.unwrap()(self.handle, Some(callback));
}
}
pub fn set_stencil_pattern(&self, mut pattern: LCDPattern) {
unsafe {
(*PLAYDATE.sprite.handle).setStencilPattern.unwrap()(self.handle, pattern.as_mut_ptr())
};
}
pub fn clear_stencil(&self) {
unsafe { (*PLAYDATE.sprite.handle).clearStencil.unwrap()(self.handle) };
}
fn get_userdata(&self) -> &SpriteData {
let ptr = unsafe { (*PLAYDATE.sprite.handle).getUserdata.unwrap()(self.handle) };
if ptr.is_null() {
let ptr = Box::into_raw(Box::<SpriteData>::default());
unsafe { (*PLAYDATE.sprite.handle).setUserdata.unwrap()(self.handle, ptr as _) };
}
let ptr = unsafe { (*PLAYDATE.sprite.handle).getUserdata.unwrap()(self.handle) };
unsafe { &*(ptr as *mut SpriteData) }
}
fn drop_userdata(&self) {
let ptr = unsafe { (*PLAYDATE.sprite.handle).getUserdata.unwrap()(self.handle) };
if !ptr.is_null() {
let _boxed = unsafe { Box::from_raw(ptr as *mut SpriteData) };
}
}
pub fn set_stencil_image(&self, stencil: impl AsRef<Bitmap>, tile: i32) {
unsafe {
(*PLAYDATE.sprite.handle).setStencilImage.unwrap()(
self.handle,
stencil.as_ref().handle,
tile,
)
};
}
pub fn check_collisions(&self, move_goal: Vec2<f32>) -> Vec<SpriteCollisionInfo> {
let mut actual_x = 0.0;
let mut actual_y = 0.0;
let mut len = 0;
let info = unsafe {
(*PLAYDATE.sprite.handle).checkCollisions.unwrap()(
self.handle,
move_goal.x,
move_goal.y,
&mut actual_x,
&mut actual_y,
&mut len,
)
};
let mut result = Vec::new();
for i in 0..len {
let info = unsafe { info.offset(i as isize).as_ref().unwrap() };
result.push(SpriteCollisionInfo::new(info));
}
PLAYDATE.system.realloc(info as _, 0);
result
}
pub fn move_with_collisions(&self, goal: Vec2<f32>) -> (Vec2<f32>, Vec<SpriteCollisionInfo>) {
let mut actual_x = 0.0;
let mut actual_y = 0.0;
let mut len = 0;
let info = unsafe {
(*PLAYDATE.sprite.handle).moveWithCollisions.unwrap()(
self.handle,
goal.x,
goal.y,
&mut actual_x,
&mut actual_y,
&mut len,
)
};
let mut result = Vec::new();
for i in 0..len {
let info = unsafe { info.offset(i as isize).as_ref().unwrap() };
result.push(SpriteCollisionInfo::new(info));
}
PLAYDATE.system.realloc(info as _, 0);
(vec2!(actual_x, actual_y), result)
}
pub fn overlapping_sprites(&self) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites =
unsafe { (*PLAYDATE.sprite.handle).overlappingSprites.unwrap()(self.handle, &mut len) };
let mut result = Vec::new();
for i in 0..len {
let sprite = unsafe { sprites.offset(i as isize).as_ref().unwrap() };
result.push(Sprite::from_ref(*sprite));
}
PLAYDATE.system.realloc(sprites as _, 0);
result
}
}
impl AsRef<Self> for Sprite {
fn as_ref(&self) -> &Self {
self
}
}
impl Drop for Sprite {
fn drop(&mut self) {
self.drop_userdata();
unsafe { (*PLAYDATE.sprite.handle).freeSprite.unwrap()(self.handle) };
}
}
impl Clone for Sprite {
fn clone(&self) -> Self {
let handle = unsafe { (*PLAYDATE.sprite.handle).copy.unwrap()(self.handle) };
Self { handle }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpriteCollisionInfo<'a> {
pub sprite: Ref<'a, Sprite>,
pub other: Ref<'a, Sprite>,
pub response_type: SpriteCollisionResponseType,
pub overlaps: u8,
pub ti: f32,
pub move_: Vec2<f32>,
pub normal: Vec2<i32>,
pub touch: Vec2<f32>,
pub sprite_rect: Rect<f32>,
pub other_rect: Rect<f32>,
}
impl<'a> SpriteCollisionInfo<'a> {
fn new(info: &sys::SpriteCollisionInfo) -> Self {
Self {
sprite: Sprite::from_ref(info.sprite),
other: Sprite::from_ref(info.other),
response_type: info.responseType,
overlaps: info.overlaps,
ti: info.ti,
move_: info.move_.into(),
normal: info.normal.into(),
touch: info.touch.into(),
sprite_rect: info.spriteRect.into(),
other_rect: info.otherRect.into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpriteQueryInfo<'a> {
pub sprite: Ref<'a, Sprite>,
pub ti1: f32,
pub ti2: f32,
pub entry_point: Vec2<f32>,
pub exit_point: Vec2<f32>,
}
impl<'a> SpriteQueryInfo<'a> {
fn new(info: &sys::SpriteQueryInfo) -> Self {
Self {
sprite: Sprite::from_ref(info.sprite),
ti1: info.ti1,
ti2: info.ti2,
entry_point: info.entryPoint.into(),
exit_point: info.exitPoint.into(),
}
}
}