BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / src / foundation.rs

foundation.rs

foundation.rs 10.4 KiB Download Raw
1//! Honest capability report for the foundation shell.
2//!
3//! Later features add import, animation, rendering and preview. Until those
4//! features land, this module must keep reporting them as not built.
5
6/// Product capability named by the design.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Capability {
9 ProjectShell,
10 ObjMtlTextures,
11 Fbx,
12 GltfGlb,
13 SkeletalAnimation,
14 AttachmentEditor,
15 PaperdollRender,
16 Preview2d,
17}
18
19/// Whether this build actually contains the capability.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Availability {
22 Present,
23 NotBuilt,
24}
25
26/// What the process should do with the command line it received.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum CliRequest {
29 OpenWindow,
30 InstallDesktop,
31 OpenProject(String),
32 PrintStatus,
33 PrintHelp,
34 PreviewFixtures,
35}
36
37/// Rejected command line. A model path is rejected on purpose: accepting one
38/// would look like import support.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum CliError {
41 UnknownArgument(String),
42 Usage(String),
43}
44
45const CAPABILITIES: [Capability; 8] = [
46 Capability::ProjectShell,
47 Capability::ObjMtlTextures,
48 Capability::Fbx,
49 Capability::GltfGlb,
50 Capability::SkeletalAnimation,
51 Capability::AttachmentEditor,
52 Capability::PaperdollRender,
53 Capability::Preview2d,
54];
55
56pub fn capabilities() -> &'static [Capability] {
57 &CAPABILITIES
58}
59
60pub fn availability(capability: Capability) -> Availability {
61 match capability {
62 Capability::ProjectShell
63 | Capability::ObjMtlTextures
64 | Capability::GltfGlb
65 | Capability::Fbx
66 | Capability::SkeletalAnimation => Availability::Present,
67 Capability::AttachmentEditor | Capability::PaperdollRender | Capability::Preview2d => {
68 Availability::NotBuilt
69 }
70 }
71}
72
73pub fn machine_key(capability: Capability) -> &'static str {
74 match capability {
75 Capability::ProjectShell => "project_shell",
76 Capability::ObjMtlTextures => "obj_mtl_textures",
77 Capability::Fbx => "fbx",
78 Capability::GltfGlb => "gltf_glb",
79 Capability::SkeletalAnimation => "skeletal_animation",
80 Capability::AttachmentEditor => "attachment_editor",
81 Capability::PaperdollRender => "paperdoll_render",
82 Capability::Preview2d => "preview_2d",
83 }
84}
85
86pub fn machine_state(availability: Availability) -> &'static str {
87 match availability {
88 Availability::Present => "present",
89 Availability::NotBuilt => "not_built",
90 }
91}
92
93pub fn dutch_label(capability: Capability) -> &'static str {
94 match capability {
95 Capability::ProjectShell => "projectschil",
96 Capability::ObjMtlTextures => "OBJ + MTL + textures",
97 Capability::Fbx => "FBX",
98 Capability::GltfGlb => "glTF/GLB",
99 Capability::SkeletalAnimation => "skeletanimatie",
100 Capability::AttachmentEditor => "3D-attachmenteditor",
101 Capability::PaperdollRender => "paperdoll-render",
102 Capability::Preview2d => "2D-preview",
103 }
104}
105
106pub fn dutch_state(availability: Availability) -> &'static str {
107 match availability {
108 Availability::Present => "aanwezig",
109 Availability::NotBuilt => "niet gebouwd",
110 }
111}
112
113pub fn dutch_summary() -> &'static str {
114 "Deze build leest glTF/GLB, FBX en OBJ+MTL, opent een .ppd-project en toont een live geskinde 3D-preview van glTF/GLB met walk en attack."
115}
116
117/// Stable status text shared by `--status` and the window.
118pub fn report() -> String {
119 let mut lines = Vec::with_capacity(2 + CAPABILITIES.len());
120 lines.push("name: Paperdoll Renderer".to_string());
121 lines.push("phase: project-editor".to_string());
122 for capability in CAPABILITIES {
123 lines.push(format!(
124 "{}: {}",
125 machine_key(capability),
126 machine_state(availability(capability))
127 ));
128 }
129 lines.join("\n")
130}
131
132/// Shared libraries the X11 window loads. `ldd` does not list them: eframe
133/// opens them at runtime. A missing one panics inside that loader, so the
134/// shell checks first.
135pub const LINUX_WINDOW_LIBRARIES: &[&str] = &[
136 "libxkbcommon-x11.so.0",
137 "libxkbcommon.so.0",
138 "libxcb.so.1",
139 "libX11.so.6",
140 "libGL.so.1",
141];
142
143pub fn missing_library_message(missing: &[&str]) -> String {
144 format!(
145 "het venster kan niet openen; deze bibliotheken ontbreken: {}.",
146 missing.join(", ")
147 )
148}
149
150#[cfg(target_os = "linux")]
151pub fn missing_window_libraries() -> Vec<&'static str> {
152 LINUX_WINDOW_LIBRARIES
153 .iter()
154 .copied()
155 .filter(|name| {
156 // Safety: `name` is a nul-free soname literal. A missing library
157 // returns Err; no symbol from the library is called.
158 unsafe { libloading::Library::new(name).is_err() }
159 })
160 .collect()
161}
162
163pub fn help_text() -> &'static str {
164 "\
165Paperdoll Renderer
166
167Gebruik:
168 paperdoll-renderer startscherm
169 paperdoll-renderer pad/naar/character.ppd open dat project
170 paperdoll-renderer --install-desktop registreer Openen met op Linux
171 paperdoll-renderer --status print de status en sluit
172 paperdoll-renderer --help toon deze tekst
173 paperdoll-renderer --preview-fixture open een voorbeeldproject op het mannelijke lichaam
174
175--status staat alleen. Een .ppd-pad of een projectmap opent character.ppd."
176}
177
178pub fn parse_cli<'a, I>(args: I) -> Result<CliRequest, CliError>
179where
180 I: IntoIterator<Item = &'a str>,
181{
182 let args: Vec<&str> = args.into_iter().collect();
183 if args == ["--install-desktop"] {
184 return Ok(CliRequest::InstallDesktop);
185 }
186 let mut help = false;
187 let mut status = false;
188 let mut preview = false;
189 let mut project = None;
190 for arg in args {
191 match arg {
192 "--help" | "-h" => help = true,
193 "--status" => status = true,
194 "--preview-fixture" => preview = true,
195 other if other.starts_with('-') => {
196 return Err(CliError::UnknownArgument(other.to_string()));
197 }
198 other => {
199 if project.is_some() {
200 return Err(CliError::Usage("geef één projectpad".to_string()));
201 }
202 project = Some(other.to_string());
203 }
204 }
205 }
206 if help {
207 return Ok(CliRequest::PrintHelp);
208 }
209 if status && (preview || project.is_some()) {
210 return Err(CliError::Usage(
211 "--status staat alleen en neemt geen projectpad aan".to_string(),
212 ));
213 }
214 if status {
215 return Ok(CliRequest::PrintStatus);
216 }
217 if preview && project.is_some() {
218 return Err(CliError::Usage(
219 "--preview-fixture opent het voorbeeld en neemt geen projectpad aan".to_string(),
220 ));
221 }
222 if preview {
223 return Ok(CliRequest::PreviewFixtures);
224 }
225 if let Some(path) = project {
226 return Ok(CliRequest::OpenProject(path));
227 }
228 Ok(CliRequest::OpenWindow)
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn import_formats_are_present_and_render_is_not() {
237 assert_eq!(
238 availability(Capability::ProjectShell),
239 Availability::Present
240 );
241 assert_eq!(availability(Capability::GltfGlb), Availability::Present);
242 assert_eq!(
243 availability(Capability::ObjMtlTextures),
244 Availability::Present
245 );
246 assert_eq!(availability(Capability::Fbx), Availability::Present);
247 assert_eq!(
248 availability(Capability::SkeletalAnimation),
249 Availability::Present
250 );
251 for capability in [
252 Capability::AttachmentEditor,
253 Capability::PaperdollRender,
254 Capability::Preview2d,
255 ] {
256 assert_eq!(
257 availability(capability),
258 Availability::NotBuilt,
259 "{capability:?} must stay not built"
260 );
261 }
262 }
263
264 #[test]
265 fn status_report_matches_the_import_build() {
266 let report = report();
267 assert!(report.starts_with("name: Paperdoll Renderer\nphase: project-editor\n"));
268 for key in [
269 "project_shell",
270 "obj_mtl_textures",
271 "gltf_glb",
272 "fbx",
273 "skeletal_animation",
274 ] {
275 assert!(
276 report.contains(&format!("{key}: present")),
277 "missing present {key} in {report}"
278 );
279 }
280 for key in ["attachment_editor", "paperdoll_render", "preview_2d"] {
281 assert!(
282 report.contains(&format!("{key}: not_built")),
283 "missing {key} in {report}"
284 );
285 assert!(
286 !report.contains(&format!("{key}: present")),
287 "{key} must not be reported as present"
288 );
289 }
290 let summary = dutch_summary();
291 assert!(summary.contains("3D-preview"));
292 assert!(summary.contains("glTF/GLB"));
293 assert!(summary.contains("walk"));
294 }
295
296 #[test]
297 fn cli_opens_a_window_or_prints_status_and_rejects_model_paths() {
298 assert_eq!(parse_cli(std::iter::empty()), Ok(CliRequest::OpenWindow));
299 assert_eq!(parse_cli(["--status"]), Ok(CliRequest::PrintStatus));
300 assert_eq!(parse_cli(["--help"]), Ok(CliRequest::PrintHelp));
301 assert_eq!(parse_cli(["-h", "--status"]), Ok(CliRequest::PrintHelp));
302 assert_eq!(
303 parse_cli(["mijn project/character.ppd"]),
304 Ok(CliRequest::OpenProject(
305 "mijn project/character.ppd".to_string()
306 ))
307 );
308 assert_eq!(
309 parse_cli(["--status", "character.ppd"]),
310 Err(CliError::Usage(
311 "--status staat alleen en neemt geen projectpad aan".to_string()
312 ))
313 );
314 assert_eq!(
315 parse_cli(["--status", "--render"]),
316 Err(CliError::UnknownArgument("--render".to_string()))
317 );
318 assert!(help_text().contains("--preview-fixture"));
319 assert_eq!(
320 parse_cli(["--preview-fixture"]),
321 Ok(CliRequest::PreviewFixtures)
322 );
323 }
324
325 #[test]
326 fn missing_library_message_names_the_library_and_does_not_claim_import() {
327 let message = missing_library_message(&["libxkbcommon-x11.so.0"]);
328 assert!(message.contains("libxkbcommon-x11.so.0"));
329 assert!(!message.to_lowercase().contains("import"));
330 assert!(!message.to_lowercase().contains("niet gebouwd"));
331 assert!(!LINUX_WINDOW_LIBRARIES.is_empty());
332 }
333}