ycraft/
res.rs

1//! Load Images, Fonts, and Sounds (i.e. resources)
2
3use std::io::{
4    Read, Seek, SeekFrom
5};
6use image::{
7    ImageBuffer, Rgba
8};
9use sdl2::{
10    mixer::{
11        Channel, Chunk, Music
12    }, pixels::{
13        Color, PixelFormatEnum
14    }, rect::Rect,
15    render::{
16        Canvas, Texture, TextureCreator
17    }, rwops::RWops,
18    surface::Surface,
19    ttf::Sdl2TtfContext,
20    video::{
21        Window, WindowContext
22    }
23};
24
25/// Container for textures with functionality for drawing to screen. This is a "resource" and does
26/// not go with GameObjects
27pub struct Image<'a> {
28    tex: Texture<'a>
29}
30
31impl<'a> Image<'a> {
32    pub fn new(
33            img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
34            creator: &'a TextureCreator<WindowContext>) -> Result<Self, String> {
35        let mut img_data = img.pixels().flat_map(|px| px.0).collect::<Vec<u8>>();
36        let pitch = img.width() * 4;
37        let sfc = Surface::from_data(
38            &mut img_data, img.width(), img.height(), pitch, PixelFormatEnum::RGBA32
39        )?;
40        let tex = Texture::from_surface(&sfc, creator).map_err(|e| e.to_string())?;
41        Ok(Self {
42            tex
43        })
44    }
45
46    pub fn render(
47            &self, cnv: &mut Canvas<Window>, src: &Rect, dest: &Rect,
48            angle: f64, flip: (bool, bool)) -> Result<(), String> {
49        cnv.copy_ex(&self.tex, Some(*src), Some(*dest), angle, None, flip.0, flip.1)
50            .map_err(|e| e.to_string())?;
51        Ok(())
52    }
53}
54
55pub struct Font<'a, 'b> {
56    font: sdl2::ttf::Font<'a, 'b>
57}
58
59impl<'a, 'b> Font<'a, 'b> {
60    pub fn new(font_data: &'b [u8], size: u16, ttf_ctx: &'a Sdl2TtfContext) -> Result<Self, String> {
61        Ok(Self {
62            font: Self::load_font_from_bytes(ttf_ctx, font_data, size)?
63        })
64    }
65
66    fn load_font_from_bytes(
67            ttf_context: &'a Sdl2TtfContext,
68            font_data: &'b [u8],
69            size: u16) -> Result<sdl2::ttf::Font<'a, 'b>, String> {
70        let font_reader = RWops::from_bytes(font_data)?;
71        let font = ttf_context.load_font_from_rwops(font_reader, size);
72        font.map_err(|e| e.to_string())
73    }
74
75    pub fn render(
76            &self, cnv: &mut Canvas<Window>, creator: &TextureCreator<WindowContext>,
77            msg: &str, color: &Color, pos: (i32, i32), angle: f64,
78            flip: (bool, bool)) -> Result<(), String> {
79        let sfc = self.font.render(msg)
80            .solid(*color)
81            .map_err(|e| e.to_string())?;
82        let tex = Texture::from_surface(&sfc, creator).map_err(|e| e.to_string())?;
83        let width = sfc.width();
84        let height = sfc.height();
85        let dest = Rect::new(pos.0, pos.1, width, height);
86        cnv.copy_ex(&tex, None, Some(dest), angle, None, flip.0, flip.1)
87    }
88}
89
90pub enum Sound<'a> {
91    Music(Music<'a>),
92    Chunk(Chunk)
93}
94
95impl<'a> Sound<'a> {
96    pub fn load_music(src: &'static [u8]) -> Result<Self, String> {
97        Ok(Self::Music(Music::from_static_bytes(src)?))
98    }
99
100    pub fn load_chunk(src: &[u8]) -> Result<Self, String> {
101        Ok(Self::Chunk(Self::load_chunk_from_bytes(src)?))
102    }
103
104    fn load_chunk_from_bytes(src: &[u8]) -> Result<sdl2::mixer::Chunk, String> {
105        let rw_ops = RWops::from_bytes(src)?;
106        let buff = Self::rwops_to_boxed_slice(rw_ops)?;
107        Chunk::from_raw_buffer(buff)
108    }
109
110    fn rwops_to_boxed_slice(mut rwops: RWops) -> Result<Box<[u8]>, String> {
111        let mut buffer = Vec::<u8>::new();
112        rwops.seek(SeekFrom::Start(0))
113            .map_err(|e| e.to_string())?;
114        rwops.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
115        let boxed_slice = buffer.into_boxed_slice();
116        Ok(boxed_slice)
117    }
118
119    pub fn is_music_playing() -> bool {
120        Music::is_playing()
121    }
122
123    pub fn pause_music() {
124        Music::pause()
125    }
126
127    pub fn resume_music() {
128        Music::resume()
129    }
130
131    pub fn halt_music() {
132        Music::halt()
133    }
134
135    pub fn play(&self) -> Result<(), String> {
136        match self {
137            Sound::Music(music) => {
138                music.play(0).map_err(|e| e.to_string())?;
139            }, Sound::Chunk(chunk) => {
140                Channel::all().play(chunk, 0).map_err(|e| e.to_string())?;
141            }
142        }
143        Ok(())
144    }
145}
146
147