use core::cell::RefCell;
use alloc::vec::Vec;
use playdate_rs_sys::LCDPattern;
use crate::{
graphics::{so2d_to_lcdrect, Bitmap},
math::{Point2D, Rect, SideOffsets2D, Vec2D},
util::Ref,
PLAYDATE,
};
pub use sys::SpriteCollisionResponseType;
fn rect_to_pdrect(rect: Rect<f32>) -> sys::PDRect {
sys::PDRect {
x: rect.origin.x,
y: rect.origin.y,
width: rect.size.width,
height: rect.size.height,
}
}
fn pdrect_to_rect(rect: sys::PDRect) -> Rect<f32> {
Rect {
origin: (rect.x, rect.y).into(),
size: (rect.width, rect.height).into(),
}
}
fn collision_point_to_point(point: sys::CollisionPoint) -> Point2D<f32> {
Point2D::new(point.x, point.y)
}
fn collision_vec_to_vec(vec: sys::CollisionVector) -> Vec2D<i32> {
Vec2D::new(vec.x, vec.y)
}
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: SideOffsets2D<i32>) {
unsafe {
(*self.handle).addDirtyRect.unwrap()(so2d_to_lcdrect(dirty_rect));
}
}
pub fn draw_sprites(&self) {
unsafe {
(*self.handle).drawSprites.unwrap()();
}
}
pub fn update_and_draw_sprites(&self) {
unsafe {
(*self.handle).updateAndDrawSprites.unwrap()();
}
}
pub fn new_sprite(&self) -> Sprite {
Sprite::from(unsafe { (*self.handle).newSprite.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: SideOffsets2D<i32>, start_z: i32, end_z: i32) {
unsafe {
(*self.handle).setClipRectsInRange.unwrap()(so2d_to_lcdrect(clip_rect), 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 check_collisions(
&self,
sprite: impl AsRef<Sprite>,
goal_x: f32,
goal_y: f32,
) -> Vec<SpriteCollisionInfo> {
let mut actual_x = 0.0;
let mut actual_y = 0.0;
let mut len = 0;
let info = unsafe {
(*self.handle).checkCollisions.unwrap()(
sprite.as_ref().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);
result
}
pub fn move_with_collisions(
&self,
sprite: impl AsRef<Sprite>,
goal_x: f32,
goal_y: f32,
) -> Vec<SpriteCollisionInfo> {
let mut actual_x = 0.0;
let mut actual_y = 0.0;
let mut len = 0;
let info = unsafe {
(*self.handle).moveWithCollisions.unwrap()(
sprite.as_ref().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);
result
}
pub fn query_sprites_at_point(&self, x: f32, y: f32) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites = unsafe { (*self.handle).querySpritesAtPoint.unwrap()(x, 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,
x: f32,
y: f32,
width: f32,
height: f32,
) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites =
unsafe { (*self.handle).querySpritesInRect.unwrap()(x, y, width, 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 overlapping_sprites(&self, sprite: impl AsRef<Sprite>) -> Vec<Ref<Sprite>> {
let mut len = 0;
let sprites =
unsafe { (*self.handle).overlappingSprites.unwrap()(sprite.as_ref().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
}
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 {
PLAYDATE.sprite.new_sprite()
}
pub fn get_position(&self) -> (f32, f32) {
let mut x = 0.0;
let mut y = 0.0;
unsafe {
(*PLAYDATE.sprite.handle).getPosition.unwrap()(self.handle, &mut x, &mut y);
}
(x, y)
}
pub fn set_bounds(&self, bounds: Rect<f32>) {
unsafe { (*PLAYDATE.sprite.handle).setBounds.unwrap()(self.handle, rect_to_pdrect(bounds)) }
}
pub fn get_bounds(&self) -> Rect<f32> {
unsafe { pdrect_to_rect((*PLAYDATE.sprite.handle).getBounds.unwrap()(self.handle)) }
}
pub fn move_to(&self, x: f32, y: f32) {
unsafe { (*PLAYDATE.sprite.handle).moveTo.unwrap()(self.handle, x, y) }
}
pub fn move_by(&self, dx: f32, dy: f32) {
unsafe { (*PLAYDATE.sprite.handle).moveBy.unwrap()(self.handle, dx, dy) }
}
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: SideOffsets2D<i32>) {
unsafe {
(*PLAYDATE.sprite.handle).setClipRect.unwrap()(self.handle, so2d_to_lcdrect(clip_rect))
};
}
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, pdrect_to_rect(bounds), pdrect_to_rect(drawrect))
}
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,
rect_to_pdrect(collide_rect),
);
}
}
pub fn get_collide_rect(&self) -> Rect<f32> {
unsafe {
pdrect_to_rect((*PLAYDATE.sprite.handle).getCollideRect.unwrap()(
self.handle,
))
}
}
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,
)
};
}
}
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_: Point2D<f32>,
pub normal: Vec2D<i32>,
pub touch: Point2D<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_: collision_point_to_point(info.move_),
normal: collision_vec_to_vec(info.normal),
touch: collision_point_to_point(info.touch),
sprite_rect: pdrect_to_rect(info.spriteRect),
other_rect: pdrect_to_rect(info.otherRect),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpriteQueryInfo<'a> {
pub sprite: Ref<'a, Sprite>,
pub ti1: f32,
pub ti2: f32,
pub entry_point: Point2D<f32>,
pub exit_point: Point2D<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: collision_point_to_point(info.entryPoint),
exit_point: collision_point_to_point(info.exitPoint),
}
}
}