#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::NonNull;
use std::borrow::Cow;
use std::ffi::{CStr, c_void};
use std::os::raw::c_char;
use crate::ffi::{
TextureInfoFfi, TextureProviderVTable, noesis_set_texture_provider,
noesis_set_texture_provider_assembly, noesis_set_texture_provider_scheme,
noesis_set_texture_provider_scheme_assembly, noesis_texture_provider_create,
noesis_texture_provider_destroy,
};
#[derive(Copy, Clone, Debug, Default)]
pub struct TextureInfo {
pub width: u32,
pub height: u32,
pub x: u32,
pub y: u32,
pub dpi_scale: f32,
}
impl TextureInfo {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
x: 0,
y: 0,
dpi_scale: 1.0,
}
}
}
pub struct ImageData<'a> {
pub width: u32,
pub height: u32,
pub bytes: &'a [u8],
}
pub trait TextureProvider: Send + Sync + 'static {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn info(&mut self, uri: &str) -> Option<TextureInfo>;
fn load(&mut self, uri: &str) -> Option<ImageData<'_>>;
}
unsafe fn provider<'a>(userdata: *mut c_void) -> &'a mut Box<dyn TextureProvider> {
&mut *userdata.cast::<Box<dyn TextureProvider>>()
}
fn cstr_to_str<'a>(p: *const c_char) -> Cow<'a, str> {
if p.is_null() {
Cow::Borrowed("")
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy()
}
}
unsafe extern "C" fn t_get_info(
userdata: *mut c_void,
uri: *const c_char,
out: *mut TextureInfoFfi,
) -> bool {
crate::panic_guard::guard(|| {
let uri = cstr_to_str(uri);
let Some(info) = provider(userdata).info(&uri) else {
return false;
};
out.write(TextureInfoFfi {
width: info.width,
height: info.height,
x: info.x,
y: info.y,
dpi_scale: info.dpi_scale,
});
true
})
}
unsafe extern "C" fn t_load_texture(
userdata: *mut c_void,
uri: *const c_char,
out_width: *mut u32,
out_height: *mut u32,
out_data: *mut *const u8,
out_len: *mut u32,
) -> bool {
crate::panic_guard::guard(|| {
let uri = cstr_to_str(uri);
let Some(img) = provider(userdata).load(&uri) else {
return false;
};
let expected = img.width.saturating_mul(img.height).saturating_mul(4) as usize;
if img.bytes.len() != expected {
return false;
}
let Ok(len) = u32::try_from(img.bytes.len()) else {
return false;
};
out_width.write(img.width);
out_height.write(img.height);
out_data.write(img.bytes.as_ptr());
out_len.write(len);
true
})
}
static VTABLE: TextureProviderVTable = TextureProviderVTable {
get_info: t_get_info,
load_texture: t_load_texture,
};
#[must_use = "dropping the guard immediately clears the registration"]
pub struct Registered {
handle: NonNull<c_void>,
userdata: NonNull<Box<dyn TextureProvider>>,
}
unsafe impl Send for Registered {}
impl Registered {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.handle.as_ptr()
}
pub fn provider_mut<P: TextureProvider>(&mut self) -> &mut P {
let boxed: &mut Box<dyn TextureProvider> = unsafe { self.userdata.as_mut() };
(**boxed)
.as_any_mut()
.downcast_mut::<P>()
.expect("Registered::provider_mut: type does not match set_texture_provider")
}
}
impl Drop for Registered {
fn drop(&mut self) {
unsafe {
noesis_texture_provider_destroy(self.handle.as_ptr());
drop(Box::from_raw(self.userdata.as_ptr()));
}
}
}
pub fn set_texture_provider<P: TextureProvider>(provider: P) -> Registered {
register_with(provider, |handle| unsafe {
noesis_set_texture_provider(handle)
})
}
fn register_with<P: TextureProvider>(provider: P, install: impl FnOnce(*mut c_void)) -> Registered {
let outer: Box<Box<dyn TextureProvider>> = Box::new(Box::new(provider));
let userdata = Box::into_raw(outer);
let handle = unsafe { noesis_texture_provider_create(&raw const VTABLE, userdata.cast()) };
let handle = NonNull::new(handle).expect("noesis_texture_provider_create returned null");
install(handle.as_ptr());
Registered {
handle,
userdata: NonNull::new(userdata).expect("Box::into_raw returned null"),
}
}
pub fn set_scheme_texture_provider<P: TextureProvider>(scheme: &str, provider: P) -> Registered {
let scheme = std::ffi::CString::new(scheme).expect("scheme contained interior NUL");
register_with(provider, move |handle| {
unsafe { noesis_set_texture_provider_scheme(scheme.as_ptr(), handle) }
})
}
pub fn set_assembly_texture_provider<P: TextureProvider>(
assembly: &str,
provider: P,
) -> Registered {
let assembly = std::ffi::CString::new(assembly).expect("assembly contained interior NUL");
register_with(provider, move |handle| {
unsafe { noesis_set_texture_provider_assembly(assembly.as_ptr(), handle) }
})
}
pub fn set_scheme_assembly_texture_provider<P: TextureProvider>(
scheme: &str,
assembly: &str,
provider: P,
) -> Registered {
let scheme = std::ffi::CString::new(scheme).expect("scheme contained interior NUL");
let assembly = std::ffi::CString::new(assembly).expect("assembly contained interior NUL");
register_with(provider, move |handle| {
unsafe {
noesis_set_texture_provider_scheme_assembly(scheme.as_ptr(), assembly.as_ptr(), handle)
}
})
}