1use plist;
2
3use std::collections::HashMap;
4
5use image::GenericImageView;
6use image::DynamicImage;
7
8fn parse_vec(str: &str) -> (i32, i32) {
10 let parts: Vec<&str> = str[1..str.len()-1].split(",").collect();
11 let a: Vec<i32> = parts
12 .iter()
13 .map(|s| s.trim().parse::<i32>().unwrap())
14 .collect();
15
16 return (a[0], a[1])
17}
18fn parse_vec_f32(str: &str) -> (f32, f32) {
20 let parts: Vec<&str> = str[1..str.len()-1].split(",").collect();
21 let a: Vec<f32> = parts
22 .iter()
23 .map(|s| s.trim().parse::<f32>().unwrap())
24 .collect();
25
26 return (a[0], a[1])
27}
28fn parse_rect_vecs(str: &str) -> ((i32, i32), (i32, i32)) {
30 let cleaned_str = str.replace("{", "").replace("}", "");
31 let parts: Vec<&str> = cleaned_str.split(",").collect();
32 let a: Vec<i32> = parts
33 .iter()
34 .map(|s| s.trim().parse::<i32>().unwrap())
35 .collect();
36
37 return ((a[0], a[1]), (a[2], a[3]))
38}
39
40#[derive(Clone, Copy, Debug)]
42pub struct Sprite {
43 pub offset: (f32, f32),
45 pub rect: ((i32, i32), (i32, i32)),
47 pub rotated: bool,
49 pub size: (i32, i32),
51 pub source_size: (i32, i32)
53}
54
55impl Sprite {
56 fn initialize(obj: plist::Value) -> Sprite {
58 let hash = obj.as_dictionary().expect("object must be a dict");
59
60 let hash_keys = vec!["spriteOffset", "spriteSize", "spriteSourceSize", "textureRect", "textureRotated"];
61
62 let isolated: Vec<(&&str, Option<&plist::Value>)> = hash_keys
63 .iter()
64 .map(|s| (s, hash.get(s)))
65 .collect();
66
67 let missing: Vec<&(&&str, Option<&plist::Value>)> = isolated
68 .iter()
69 .filter(|&&(_, value)| value.is_none())
70 .collect();
71
72 if !missing.is_empty() {
73 let missing_entries: Vec<&str> = missing.iter().map(|(&key, _)| key).collect();
74 panic!("missing entries: {:?}", missing_entries);
75 }
76
77 let isolated_hash: HashMap<String, plist::Value> = isolated
78 .iter()
79 .map(|&(key, value)| (key.to_string(), value.expect("value is none after checking").clone()))
80 .collect();
81
82 return Sprite {
83 offset: parse_vec_f32(isolated_hash.get("spriteOffset").expect("missing spriteOffset").as_string().expect("spriteOffset is not a string")),
84 rect: parse_rect_vecs(isolated_hash.get("textureRect").expect("missing textureRect").as_string().expect("textureRect is not a string")),
85 rotated: isolated_hash.get("textureRotated").unwrap_or(&plist::Value::from(false)).as_boolean().expect("textureRotated is not a boolean").clone(),
86 size: parse_vec(isolated_hash.get("spriteSize").expect("missing spriteSize").as_string().expect("spriteSize is not a string")),
87 source_size: parse_vec(isolated_hash.get("spriteSourceSize").expect("missing spriteSourceSize").as_string().expect("spriteSourceSize is not a string"))
88 }
89 }
90}
91
92#[derive(Clone)]
94pub struct Spritesheet {
95 pub sprites: HashMap<String, Sprite>,
96
97 pub texture_file_name: String,
98 pub size: (i32, i32)
99}
100
101impl Spritesheet {
102 fn initialize(obj: plist::Value) -> Spritesheet {
104 let hash = obj.as_dictionary().expect("object must be a dict");
105
106 let sprites = hash.get("frames").expect("object must have a `frames` object").as_dictionary().expect("`frames` must be a dict");
107 let metadata = hash.get("metadata").expect("object must have a `metadata` object").as_dictionary().expect("`metadata` must be a dict");
108
109 return Spritesheet {
110 sprites: sprites.iter().map(|(key, value)| (key.clone(), Sprite::initialize(value.clone()))).collect(),
111 texture_file_name: metadata.get("textureFileName").expect("metadata must have a `textureFileName` object").as_string().expect("`textureFileName` must be a string").to_string(),
112 size: parse_vec(metadata.get("size").expect("metadata must have a `size` object").as_string().expect("`size` must be a string"))
113 }
114 }
115}
116
117#[derive(Clone)]
119pub struct LoadedSpritesheet {
120 pub spritesheet: Spritesheet,
121 pub texture: DynamicImage
122}
123
124pub fn load_spritesheet(path: &str) -> LoadedSpritesheet {
126 return LoadedSpritesheet {
127 spritesheet: Spritesheet::initialize(plist::from_file(path).expect("could not load plist")),
128 texture: image::open(path.replace(".plist", ".png")).expect("could not load texture")
129 }
130}
131
132#[derive(Clone, Debug)]
134pub struct AnimationSprite {
135 pub texture: String,
136 pub position: (f32, f32),
137 pub scale: (f32, f32),
138 pub rotation: f64,
139 pub flipped: (bool, bool),
140 pub z: i32
141}
142
143impl AnimationSprite {
144 fn initialize(obj: plist::Value) -> AnimationSprite {
145 let hash = obj.as_dictionary().expect("object must be a dict");
146
147 let hash_keys = vec!["texture", "position", "scale", "rotation", "flipped", "zValue"];
148
149 let isolated: Vec<(&&str, Option<&plist::Value>)> = hash_keys
150 .iter()
151 .map(|s| (s, hash.get(s)))
152 .collect();
153
154 let missing: Vec<&(&&str, Option<&plist::Value>)> = isolated
155 .iter()
156 .filter(|&&(_, value)| value.is_none())
157 .collect();
158
159 if !missing.is_empty() {
160 let missing_entries: Vec<&str> = missing.iter().map(|(&key, _)| key).collect();
161 panic!("missing entries: {:?}", missing_entries);
162 }
163
164 let isolated_hash: HashMap<String, plist::Value> = isolated
165 .iter()
166 .map(|&(key, value)| (key.to_string(), value.expect("value is none after checking").clone()))
167 .collect();
168
169 return AnimationSprite {
170 texture: isolated_hash.get("texture").expect("missing texture").as_string().expect("texture is not a string").to_string(),
171 position: parse_vec_f32(isolated_hash.get("position").expect("missing position").as_string().expect("position is not a string")),
172 scale: parse_vec_f32(isolated_hash.get("scale").expect("missing scale").as_string().expect("scale is not a string")),
173 rotation: isolated_hash.get("rotation").expect("missing rotation").as_string().expect("rotation is not a string").parse::<f64>().expect("couldnt parse rotation as f64"),
174 flipped: {
175 let flipped_numbers = parse_vec(isolated_hash.get("flipped").expect("missing flipped").as_string().expect("flipped is not a string"));
176 (flipped_numbers.0 > 0, flipped_numbers.1 > 0)
177 },
178 z: isolated_hash.get("zValue").expect("missing zValue").as_string().expect("zValue is not a string").parse::<i32>().expect("couldnt parse zValue as i32")
179 }
180 }
181}
182
183pub type Animations = HashMap<String, Vec<AnimationSprite>>;
184
185pub fn load_animations(path: &str) -> Animations {
186 let loaded_plist: plist::Value = plist::from_file(path).expect("could not load plist");
187 let animations = loaded_plist.as_dictionary().expect("object must be a dict").get("animationContainer").expect("key `animationContainer` doesnt exist").as_dictionary().expect("`animationContainer` must be a dict");
188 let mut parsed_animations: Animations = HashMap::new();
189 for (k, v) in animations.iter() {
190 parsed_animations.insert(k.clone(), vec![] as Vec<AnimationSprite>);
191 parsed_animations.get_mut(k.as_str()).expect("this should exist..")
192 .extend(v.as_dictionary().expect("animation must be a dict")
193 .iter().map(|(_, v)| AnimationSprite::initialize(v.clone())));
194 }
195 return parsed_animations;
196}
197
198pub fn get_sprite(spritesheet: Spritesheet, img: &DynamicImage, key: String) -> Option<(DynamicImage, Sprite)> {
200 let sprite = spritesheet.sprites.get(&key);
201
202 if sprite.is_none() {
203 return None;
204 }
205
206 if let Some(sprite) = sprite {
207 let rect = sprite.rect;
208
209 let (mut left, mut top, mut width, mut height) = (rect.0.0, rect.0.1, rect.1.0, rect.1.1);
210 if sprite.rotated {
211 (left, top, width, height) = (left, top, height, width);
212 }
213
214 let mut canvas: DynamicImage = image::DynamicImage::ImageRgba8(img.view(left as u32, top as u32, width as u32, height as u32).to_image());
215
216 if sprite.rotated {
217 canvas = canvas.rotate270();
218 }
219
220 return Some((canvas, sprite.clone()));
221 }
222
223 unreachable!("The sprite should have been found in the spritesheet or not found at all")
224}
225
226pub fn get_sprite_from_loaded(spritesheet: &LoadedSpritesheet, key: String) -> Option<(DynamicImage, Sprite)> {
228 let sprite = get_sprite(spritesheet.spritesheet.clone(), &spritesheet.texture, key);
229 return sprite;
230}