Skip to main content

khora_editor/widgets/
enum_variants.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Registry of inspector-editable enum variants.
16//!
17//! The JSON-driven inspector walks `serde_json::Value`s and renders widgets
18//! by shape. Single-key objects are typically serde-tagged enum variants:
19//! e.g. `{"Directional": {...}}` for `LightType::Directional(_)`. To let
20//! the user *switch* the variant from the inspector, we need (1) the list
21//! of valid variant names and (2) a default-serialized payload for each.
22//!
23//! That information cannot be reliably derived from the JSON alone (the
24//! type information is lost once we serialize to `serde_json::Value`), so
25//! this module hard-codes the small set of enums the inspector currently
26//! knows how to switch. It is **opt-in**: enums not registered here keep
27//! the previous read-only `Variant: <key>` label.
28//!
29//! A future iteration could move this generation into the `#[derive(Component)]`
30//! macro itself (emit `enum_variants_for(field_path)` per field whose type
31//! is an enum). For now, the registry is the smallest honest fix.
32
33use khora_sdk::khora_core::renderer::light::{DirectionalLight, LightType, PointLight, SpotLight};
34use serde_json::Value;
35use std::sync::OnceLock;
36
37/// Returns the editable variant set for a single-key JSON object whose key
38/// matches a known enum variant, or `None` if the key isn't recognised.
39///
40/// Each entry is `(variant_name, full_default_json)`. The
41/// `full_default_json` is the entire `{"VariantName": {...}}` object —
42/// callers replace the current map with it on selection.
43pub fn editable_variants(current_key: &str) -> Option<&'static [(&'static str, Value)]> {
44    static REGISTRY: OnceLock<Vec<RegisteredEnum>> = OnceLock::new();
45    let registry = REGISTRY.get_or_init(build_registry);
46    registry
47        .iter()
48        .find(|e| e.variants.iter().any(|(name, _)| *name == current_key))
49        .map(|e| e.variants.as_slice())
50}
51
52struct RegisteredEnum {
53    variants: Vec<(&'static str, Value)>,
54}
55
56fn build_registry() -> Vec<RegisteredEnum> {
57    vec![
58        // LightType — Directional / Point / Spot (Light::light_type).
59        RegisteredEnum {
60            variants: vec![
61                (
62                    "Directional",
63                    serde_json::to_value(LightType::Directional(DirectionalLight::default()))
64                        .expect("Directional default serialises"),
65                ),
66                (
67                    "Point",
68                    serde_json::to_value(LightType::Point(PointLight::default()))
69                        .expect("Point default serialises"),
70                ),
71                (
72                    "Spot",
73                    serde_json::to_value(LightType::Spot(SpotLight::default()))
74                        .expect("Spot default serialises"),
75                ),
76            ],
77        },
78    ]
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn known_light_keys_are_recognised() {
87        for key in ["Directional", "Point", "Spot"] {
88            let variants = editable_variants(key).unwrap_or_else(|| panic!("missing {}", key));
89            assert_eq!(variants.len(), 3);
90        }
91    }
92
93    #[test]
94    fn unknown_keys_return_none() {
95        assert!(editable_variants("Bogus").is_none());
96        assert!(editable_variants("").is_none());
97    }
98
99    #[test]
100    fn variant_payloads_are_single_key_objects() {
101        for (_, default) in editable_variants("Directional").unwrap() {
102            let Value::Object(map) = default else {
103                panic!("default not an object");
104            };
105            assert_eq!(map.len(), 1);
106        }
107    }
108}