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 if let Ok(home) = std::env::var("HOME") {
112 let current = PathBuf::from(home).join(".flatland3/assets/current");
113 if current.join("manifest.yaml").is_file() {
114 return Some(current);
115 }
116 }
117 let mut dir = std::env::current_dir().ok()?;
118 for _ in 0..8 {
119 let candidate = dir.join("assets/gfx/sprites");
120 if candidate.join("manifest.yaml").is_file() {
121 return Some(candidate);
122 }
123 if !dir.pop() {
124 break;
125 }
126 }
127 None
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn loads_starter_sprite_catalog() {
136 let dir = default_sprites_dir().expect("sprites dir");
137 let cat = load_sprite_catalog(&dir).expect("load");
138 assert!(!cat.is_empty());
139 let modes = cat.mode_ids("resource.oak_log");
140 assert!(modes.contains(&"idle".to_string()));
141 assert_eq!(modes.len(), 1);
142 }
143}