use crate::{
StereoKitError,
maths::{Matrix, Vec2},
sk::MainThreadToken,
system::{IAsset, Pivot},
tex::{Tex, TexT},
util::Color32,
};
use std::{
ffi::{CStr, CString, c_char},
path::Path,
ptr::NonNull,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum SpriteType {
Atlased = 0,
Single = 1,
}
#[repr(C)]
#[derive(Debug, PartialEq)]
pub struct Sprite(pub NonNull<_SpriteT>);
impl Drop for Sprite {
fn drop(&mut self) {
unsafe { sprite_release(self.0.as_ptr()) };
}
}
impl AsRef<Sprite> for Sprite {
fn as_ref(&self) -> &Sprite {
self
}
}
#[repr(C)]
#[derive(Debug)]
pub struct _SpriteT {
_unused: [u8; 0],
}
pub type SpriteT = *mut _SpriteT;
unsafe extern "C" {
pub fn sprite_find(id: *const c_char) -> SpriteT;
pub fn sprite_create(sprite: TexT, type_: SpriteType, atlas_id: *const c_char) -> SpriteT;
pub fn sprite_create_file(filename_utf8: *const c_char, type_: SpriteType, atlas_id: *const c_char) -> SpriteT;
pub fn sprite_set_id(sprite: SpriteT, id: *const c_char);
pub fn sprite_get_id(sprite: SpriteT) -> *const c_char;
pub fn sprite_addref(sprite: SpriteT);
pub fn sprite_release(sprite: SpriteT);
pub fn sprite_get_aspect(sprite: SpriteT) -> f32;
pub fn sprite_get_width(sprite: SpriteT) -> i32;
pub fn sprite_get_height(sprite: SpriteT) -> i32;
pub fn sprite_get_dimensions_normalized(sprite: SpriteT) -> Vec2;
pub fn sprite_draw(sprite: SpriteT, transform: Matrix, pivot_position: Pivot, color: Color32);
}
impl IAsset for Sprite {
fn get_id(&self) -> &str {
self.get_id()
}
}
impl Default for Sprite {
fn default() -> Self {
Self::close()
}
}
impl Sprite {
pub fn from_tex(
sprite_tex: impl AsRef<Tex>,
sprite_type: Option<SpriteType>,
atlas_id: Option<String>,
) -> Result<Sprite, StereoKitError> {
let sprite_type = sprite_type.unwrap_or(SpriteType::Atlased);
let atlas_id = match atlas_id {
Some(s) => s,
None => "default".to_owned(),
};
let c_atlas_id = CString::new(atlas_id)?;
Ok(Sprite(
NonNull::new(unsafe { sprite_create(sprite_tex.as_ref().0.as_ptr(), sprite_type, c_atlas_id.as_ptr()) })
.ok_or(StereoKitError::SpriteCreate)?,
))
}
pub fn from_file(
file_utf8: impl AsRef<Path>,
sprite_type: Option<SpriteType>,
atlas_id: Option<&str>,
) -> Result<Sprite, StereoKitError> {
let sprite_type = sprite_type.unwrap_or(SpriteType::Atlased);
let atlas_id = match atlas_id {
Some(s) => s.to_owned(),
None => "default".to_owned(),
};
let c_atlas_id = CString::new(atlas_id)?;
let path_buf = file_utf8.as_ref().to_path_buf();
let c_str = CString::new(path_buf.clone().to_str().ok_or(StereoKitError::SpriteFile(path_buf.clone()))?)?;
Ok(Sprite(
NonNull::new(unsafe { sprite_create_file(c_str.as_ptr(), sprite_type, c_atlas_id.as_ptr()) })
.ok_or(StereoKitError::SpriteFile(path_buf))?,
))
}
pub fn find<S: AsRef<str>>(id: S) -> Result<Sprite, StereoKitError> {
let cstr_id = CString::new(id.as_ref())?;
Ok(Sprite(
NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) })
.ok_or(StereoKitError::SpriteFind(id.as_ref().to_string(), "sprite_find failed".to_string()))?,
))
}
pub fn clone_ref(&self) -> Sprite {
Sprite(
NonNull::new(unsafe { sprite_find(sprite_get_id(self.0.as_ptr())) }).expect("<asset>::clone_ref failed!"),
)
}
pub fn id<S: AsRef<str>>(&mut self, id: S) -> &mut Self {
let cstr_id = CString::new(id.as_ref()).unwrap();
unsafe { sprite_set_id(self.0.as_ptr(), cstr_id.as_ptr()) };
self
}
pub fn draw(
&self,
_token: &MainThreadToken,
transform: impl Into<Matrix>,
pivot_position: Pivot,
linear_color: Option<Color32>,
) {
let color_linear = linear_color.unwrap_or(Color32::WHITE);
unsafe { sprite_draw(self.0.as_ptr(), transform.into(), pivot_position, color_linear) };
}
pub fn get_id(&self) -> &str {
unsafe { CStr::from_ptr(sprite_get_id(self.0.as_ptr())) }.to_str().unwrap()
}
pub fn get_aspect(&self) -> f32 {
unsafe { sprite_get_aspect(self.0.as_ptr()) }
}
pub fn get_height(&self) -> i32 {
unsafe { sprite_get_height(self.0.as_ptr()) }
}
pub fn get_width(&self) -> i32 {
unsafe { sprite_get_width(self.0.as_ptr()) }
}
pub fn get_normalized_dimensions(&self) -> Vec2 {
unsafe { sprite_get_dimensions_normalized(self.0.as_ptr()) }
}
pub fn radio_on() -> Self {
let cstr_id = CString::new("sk/ui/radio_on").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn radio_off() -> Self {
let cstr_id = CString::new("sk/ui/radio_off").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn toggle_on() -> Self {
let cstr_id = CString::new("sk/ui/toggle_on").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn toggle_off() -> Self {
let cstr_id = CString::new("sk/ui/toggle_off").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn arrow_left() -> Self {
let cstr_id = CString::new("sk/ui/arrow_left").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn arrow_right() -> Self {
let cstr_id = CString::new("sk/ui/arrow_right").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn arrow_up() -> Self {
let cstr_id = CString::new("sk/ui/arrow_up").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn arrow_down() -> Self {
let cstr_id = CString::new("sk/ui/arrow_down").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn backspace() -> Self {
let cstr_id = CString::new("sk/ui/backspace").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn shift() -> Self {
let cstr_id = CString::new("sk/ui/shift").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn close() -> Self {
let cstr_id = CString::new("sk/ui/close").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn list() -> Self {
let cstr_id = CString::new("sk/ui/list").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
pub fn grid() -> Self {
let cstr_id = CString::new("sk/ui/grid").unwrap();
Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
}
}