Skip to main content

flatland_presentation/
catalog.rs

1//! Load gfx sprite sheet mode lists from `assets/gfx/sprites/`.
2
3use 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/// Mode ids for one sprite sheet.
34#[derive(Debug, Clone, Default)]
35pub struct SpriteSheetModes {
36    pub sheet_id: String,
37    pub mode_ids: Vec<String>,
38}
39
40/// Index of all sprite sheets and their mode ids.
41#[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
67/// Load sprite mode index from `dir` (expects `manifest.yaml` + sheet YAML files).
68pub 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
103/// Resolve sprites directory from env or by walking up from cwd.
104pub 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}