flatland_presentation/
catalog.rs1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use serde::Deserialize;
7
8#[derive(Debug, Clone, Deserialize)]
9struct SpriteManifest {
10 #[allow(dead_code)]
11 version: u32,
12 sheets: Vec<SpriteManifestEntry>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
16struct SpriteManifestEntry {
17 #[allow(dead_code)]
18 id: String,
19 file: String,
20}
21
22#[derive(Debug, Clone, Deserialize)]
23struct SpriteSheetDef {
24 id: String,
25 modes: Vec<SpriteModeDef>,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29struct SpriteModeDef {
30 id: String,
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct SpriteSheetModes {
36 pub sheet_id: String,
37 pub mode_ids: Vec<String>,
38}
39
40#[derive(Debug, Clone, Default)]
42pub struct SpriteCatalog {
43 sheets: HashMap<String, SpriteSheetModes>,
44}
45
46impl SpriteCatalog {
47 pub fn mode_ids(&self, sheet_id: &str) -> &[String] {
48 self.sheets
49 .get(sheet_id)
50 .map(|s| s.mode_ids.as_slice())
51 .unwrap_or(&[])
52 }
53
54 pub fn sheet_ids(&self) -> impl Iterator<Item = &String> {
55 self.sheets.keys()
56 }
57
58 pub fn len(&self) -> usize {
59 self.sheets.len()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.sheets.is_empty()
64 }
65}
66
67pub fn load_sprite_catalog(dir: &Path) -> anyhow::Result<SpriteCatalog> {
69 let manifest_path = dir.join("manifest.yaml");
70 if !manifest_path.is_file() {
71 return Ok(SpriteCatalog::default());
72 }
73 let text = std::fs::read_to_string(&manifest_path)?;
74 let manifest: SpriteManifest = serde_yaml::from_str(&text)?;
75 let mut sheets = HashMap::new();
76
77 for entry in &manifest.sheets {
78 let sheet_path = dir.join(&entry.file);
79 let sheet_text = match std::fs::read_to_string(&sheet_path) {
80 Ok(t) => t,
81 Err(_) => continue,
82 };
83 let def: SpriteSheetDef = match serde_yaml::from_str(&sheet_text) {
84 Ok(d) => d,
85 Err(_) => continue,
86 };
87 let mode_ids: Vec<String> = def.modes.into_iter().map(|m| m.id).collect();
88 if mode_ids.is_empty() {
89 continue;
90 }
91 sheets.insert(
92 def.id.clone(),
93 SpriteSheetModes {
94 sheet_id: def.id,
95 mode_ids,
96 },
97 );
98 }
99
100 Ok(SpriteCatalog { sheets })
101}
102
103pub fn default_sprites_dir() -> Option<PathBuf> {
105 if let Ok(p) = std::env::var("FLATLAND_SPRITES_DIR") {
106 let path = PathBuf::from(p);
107 if path.join("manifest.yaml").is_file() {
108 return Some(path);
109 }
110 }
111 let mut dir = std::env::current_dir().ok()?;
112 for _ in 0..8 {
113 let candidate = dir.join("assets/gfx/sprites");
114 if candidate.join("manifest.yaml").is_file() {
115 return Some(candidate);
116 }
117 if !dir.pop() {
118 break;
119 }
120 }
121 None
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn loads_starter_sprite_catalog() {
130 let dir = default_sprites_dir().expect("sprites dir");
131 let cat = load_sprite_catalog(&dir).expect("load");
132 assert!(!cat.is_empty());
133 let modes = cat.mode_ids("resource.oak_log");
134 assert!(modes.contains(&"idle".to_string()));
135 assert_eq!(modes.len(), 1);
136 }
137}