use std::cell::Cell;
use std::path::Path;
use std::rc::Rc;
use crate::error::Result;
use crate::graphics::{self, DrawParams, ImageData, Rectangle};
use crate::platform::{GraphicsDevice, RawTexture};
use crate::Context;
#[derive(Debug)]
pub(crate) struct TextureSharedData {
pub(crate) handle: RawTexture,
filter_mode: Cell<FilterMode>,
}
impl PartialEq for TextureSharedData {
fn eq(&self, other: &TextureSharedData) -> bool {
self.handle.eq(&other.handle)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Texture {
pub(crate) data: Rc<TextureSharedData>,
}
impl Texture {
pub fn new<P>(ctx: &mut Context, path: P) -> Result<Texture>
where
P: AsRef<Path>,
{
let data = ImageData::new(path)?;
Texture::from_image_data(ctx, &data)
}
pub fn from_data(
ctx: &mut Context,
width: i32,
height: i32,
format: TextureFormat,
data: &[u8],
) -> Result<Texture> {
Texture::with_device(
&mut ctx.device,
width,
height,
data,
format,
ctx.graphics.default_filter_mode,
)
}
pub fn from_encoded(ctx: &mut Context, data: &[u8]) -> Result<Texture> {
let data = ImageData::from_encoded(data)?;
Texture::from_image_data(ctx, &data)
}
pub fn from_image_data(ctx: &mut Context, data: &ImageData) -> Result<Texture> {
Texture::from_data(
ctx,
data.width(),
data.height(),
TextureFormat::Rgba8,
data.as_bytes(),
)
}
pub(crate) fn from_raw(handle: RawTexture, filter_mode: FilterMode) -> Texture {
Texture {
data: Rc::new(TextureSharedData {
handle,
filter_mode: Cell::new(filter_mode),
}),
}
}
pub(crate) fn with_device(
device: &mut GraphicsDevice,
width: i32,
height: i32,
data: &[u8],
format: TextureFormat,
filter_mode: FilterMode,
) -> Result<Texture> {
let handle = device.new_texture(width, height, format, filter_mode)?;
device.set_texture_data(&handle, data, 0, 0, width, height)?;
Ok(Texture {
data: Rc::new(TextureSharedData {
handle,
filter_mode: Cell::new(filter_mode),
}),
})
}
pub(crate) fn with_device_empty(
device: &mut GraphicsDevice,
width: i32,
height: i32,
filter_mode: FilterMode,
) -> Result<Texture> {
let data = vec![0; (width * height * 4) as usize];
Texture::with_device(
device,
width,
height,
&data,
TextureFormat::Rgba8,
filter_mode,
)
}
pub fn draw<P>(&self, ctx: &mut Context, params: P)
where
P: Into<DrawParams>,
{
let params = params.into();
graphics::set_texture(ctx, self);
graphics::push_quad(
ctx,
0.0,
0.0,
self.width() as f32,
self.height() as f32,
0.0,
0.0,
1.0,
1.0,
¶ms,
);
}
pub fn draw_region<P>(&self, ctx: &mut Context, region: Rectangle, params: P)
where
P: Into<DrawParams>,
{
let params = params.into();
let texture_width = self.width() as f32;
let texture_height = self.height() as f32;
graphics::set_texture(ctx, self);
graphics::push_quad(
ctx,
0.0,
0.0,
region.width,
region.height,
region.x / texture_width,
region.y / texture_height,
region.right() / texture_width,
region.bottom() / texture_height,
¶ms,
);
}
pub fn draw_nine_slice<P>(
&self,
ctx: &mut Context,
config: &NineSlice,
width: f32,
height: f32,
params: P,
) where
P: Into<DrawParams>,
{
let params = params.into();
let texture_width = self.width() as f32;
let texture_height = self.height() as f32;
let x1 = 0.0;
let y1 = 0.0;
let x2 = config.left;
let y2 = config.top;
let x3 = width - config.right;
let y3 = height - config.bottom;
let x4 = width;
let y4 = height;
let u1 = config.region.x / texture_width;
let v1 = config.region.y / texture_height;
let u2 = (config.region.x + config.left) / texture_width;
let v2 = (config.region.y + config.top) / texture_height;
let u3 = (config.region.x + config.region.width - config.right) / texture_width;
let v3 = (config.region.y + config.region.height - config.bottom) / texture_height;
let u4 = (config.region.x + config.region.width) / texture_width;
let v4 = (config.region.y + config.region.height) / texture_height;
graphics::set_texture(ctx, self);
graphics::push_quad(ctx, x1, y1, x2, y2, u1, v1, u2, v2, ¶ms);
graphics::push_quad(ctx, x2, y1, x3, y2, u2, v1, u3, v2, ¶ms);
graphics::push_quad(ctx, x3, y1, x4, y2, u3, v1, u4, v2, ¶ms);
graphics::push_quad(ctx, x1, y2, x2, y3, u1, v2, u2, v3, ¶ms);
graphics::push_quad(ctx, x2, y2, x3, y3, u2, v2, u3, v3, ¶ms);
graphics::push_quad(ctx, x3, y2, x4, y3, u3, v2, u4, v3, ¶ms);
graphics::push_quad(ctx, x1, y3, x2, y4, u1, v3, u2, v4, ¶ms);
graphics::push_quad(ctx, x2, y3, x3, y4, u2, v3, u3, v4, ¶ms);
graphics::push_quad(ctx, x3, y3, x4, y4, u3, v3, u4, v4, ¶ms);
}
pub fn width(&self) -> i32 {
self.data.handle.width()
}
pub fn height(&self) -> i32 {
self.data.handle.height()
}
pub fn size(&self) -> (i32, i32) {
(self.data.handle.width(), self.data.handle.height())
}
pub fn format(&self) -> TextureFormat {
self.data.handle.format()
}
pub fn filter_mode(&self) -> FilterMode {
self.data.filter_mode.get()
}
pub fn set_filter_mode(&mut self, ctx: &mut Context, filter_mode: FilterMode) {
ctx.device
.set_texture_filter_mode(&self.data.handle, filter_mode);
self.data.filter_mode.set(filter_mode);
}
pub fn get_data(&self, ctx: &mut Context) -> ImageData {
let (width, height) = self.size();
let buffer = ctx.device.get_texture_data(&self.data.handle);
ImageData::from_data(width, height, self.format(), buffer)
.expect("buffer should be exact size for image")
}
pub fn set_data(
&self,
ctx: &mut Context,
x: i32,
y: i32,
width: i32,
height: i32,
data: &[u8],
) -> Result {
ctx.device
.set_texture_data(&self.data.handle, data, x, y, width, height)
}
pub fn replace_data(&self, ctx: &mut Context, data: &[u8]) -> Result {
let (width, height) = self.size();
self.set_data(ctx, 0, 0, width, height, data)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TextureFormat {
Rgba8,
R8,
Rg8,
Rgba16F,
}
impl TextureFormat {
pub fn stride(self) -> usize {
match self {
TextureFormat::Rgba8 => 4,
TextureFormat::R8 => 1,
TextureFormat::Rg8 => 2,
TextureFormat::Rgba16F => 8,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterMode {
Nearest,
Linear,
}
#[derive(Debug, Clone)]
pub struct NineSlice {
pub region: Rectangle,
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
impl NineSlice {
pub fn new(region: Rectangle, left: f32, right: f32, top: f32, bottom: f32) -> NineSlice {
NineSlice {
region,
left,
right,
top,
bottom,
}
}
pub fn with_border(region: Rectangle, border: f32) -> NineSlice {
NineSlice {
region,
left: border,
right: border,
top: border,
bottom: border,
}
}
}