1
use std::collections::HashMap;
2
use std::fmt::Debug;
3
use std::hash::Hash;
4
use std::sync::Arc;
5

            
6
use crate::math::{Point, Rect, Size};
7
use crate::sprite::{SpriteCollection, SpriteMap, SpriteSource};
8
use crate::texture::Texture;
9

            
10
/// A collection of sprites from a single [`Texture`].
11
#[derive(Debug, Clone)]
12
pub struct SpriteSheet<T>
13
where
14
    T: Debug,
15
{
16
    /// The source texture.
17
    pub texture: Texture,
18
    data: Arc<SpriteSheetData<T>>,
19
}
20

            
21
#[derive(Debug)]
22
struct SpriteSheetData<T>
23
where
24
    T: Debug,
25
{
26
    tile_size: Size<u32>,
27
    sprites: HashMap<T, Rect<u32>>,
28
}
29

            
30
impl<T> SpriteSheet<T>
31
where
32
    T: Debug + Eq + Hash,
33
{
34
    /// Creates a new sprite sheet, diving `texture` into a grid of `tile_size`.
35
    /// The order of `tiles` will be read left-to-right, top-to-bottom.
36
    #[must_use]
37
    pub fn new(texture: Texture, tile_size: Size<u32>, tiles: Vec<T>) -> Self {
38
        let dimensions = divide_size(texture.size().cast(), tile_size);
39
        Self {
40
            texture,
41
            data: Arc::new(SpriteSheetData::from_tiles(tiles, tile_size, dimensions)),
42
        }
43
    }
44

            
45
    /// Returns the size of the tiles within this sheet.
46
    #[must_use]
47
    pub fn tile_size(&self) -> Size<u32> {
48
        self.data.tile_size
49
    }
50

            
51
    /// Returns the sprites identified by each element in `iterator`.
52
    ///
53
    /// # Panics
54
    ///
55
    /// Panics if a tile isn't found.
56
    #[must_use]
57
    pub fn sprites<I: IntoIterator<Item = T>>(&self, iterator: I) -> Vec<SpriteSource> {
58
        iterator
59
            .into_iter()
60
            .map(|tile| {
61
                let location = self.data.sprites.get(&tile).unwrap();
62
                SpriteSource::new(*location, self.texture.clone())
63
            })
64
            .collect()
65
    }
66

            
67
    /// Returns the sprites identified by each element in `iterator` into a
68
    /// [`SpriteMap`].
69
    #[must_use]
70
    pub fn sprite_map<I: IntoIterator<Item = T>>(&self, iterator: I) -> SpriteMap<T> {
71
        let map = iterator
72
            .into_iter()
73
            .map(|tile| {
74
                let location = self.data.sprites.get(&tile).unwrap();
75
                (tile, SpriteSource::new(*location, self.texture.clone()))
76
            })
77
            .collect::<HashMap<_, _>>();
78
        SpriteMap::new(map)
79
    }
80
}
81

            
82
const fn divide_size(a: Size<u32>, b: Size<u32>) -> Size<u32> {
83
    Size::new(a.width / b.width, a.height / b.height)
84
}
85

            
86
impl<T: Debug + Eq + Hash> SpriteSheetData<T> {
87
    fn from_tiles(tiles: Vec<T>, tile_size: Size<u32>, dimensions: Size<u32>) -> Self {
88
        let mut sprites = HashMap::new();
89

            
90
        for (index, tile) in tiles.into_iter().enumerate() {
91
            let index = index as u32;
92
            let y = index / dimensions.width;
93
            let x = index - y * dimensions.width;
94
            sprites.insert(
95
                tile,
96
                Rect::new(
97
                    Point::new(x * tile_size.width, y * tile_size.height),
98
                    tile_size,
99
                ),
100
            );
101
        }
102

            
103
        Self { tile_size, sprites }
104
    }
105
}
106

            
107
impl<T> SpriteSheet<T>
108
where
109
    T: Clone + Debug + Eq + Hash,
110
{
111
    /// Returns a collection of all tiles in the sheet  as
112
    #[must_use]
113
    pub fn to_sprite_map(&self) -> SpriteMap<T> {
114
        SpriteMap::new(
115
            self.data
116
                .sprites
117
                .clone()
118
                .iter()
119
                .map(|(tile, location)| {
120
                    (
121
                        tile.clone(),
122
                        SpriteSource::new(*location, self.texture.clone()),
123
                    )
124
                })
125
                .collect(),
126
        )
127
    }
128
}
129

            
130
impl<T> SpriteCollection<T> for SpriteSheet<T>
131
where
132
    T: Debug + Send + Sync + Eq + Hash,
133
{
134
    fn sprite(&self, tile: &T) -> Option<SpriteSource> {
135
        let location = self.data.sprites.get(tile);
136
        location.map(|location| SpriteSource::new(*location, self.texture.clone()))
137
    }
138
}