Skip to main content

diy_panel/
diy-panel.rs

1use mittens_engine::{engine, engine::ecs::SignalEmitter, scripting, utils};
2
3const DEBUG_CLONE_WORLD_OFFSET: [f32; 3] = [0.0, 0.0, 0.1];
4
5fn spawn_runtime_text(
6    universe: &mut engine::Universe,
7    owner: engine::ecs::ComponentId,
8    label: &str,
9) {
10    use engine::ecs::component::{
11        ColorComponent, EdgeInsets, SizeDimension, StyleComponent, TextComponent,
12        TransformComponent,
13    };
14
15    let root = universe.world.add_component_boxed_named(
16        label,
17        Box::new(TransformComponent::new().with_position(0.0, 0.0, 0.2)),
18    );
19    let style = universe.world.add_component_boxed_named(
20        format!("{label}_style"),
21        Box::new({
22            let mut style = StyleComponent::new();
23            style.margin = EdgeInsets::axes(0.25, 0.25);
24            style.padding = EdgeInsets::axes(0.5, 0.5);
25            style.height = SizeDimension::GlyphUnits(2.5);
26            style.width = SizeDimension::Auto;
27            style.background_color = Some([1.0, 0.80, 0.80, 1.0]);
28            style
29        }),
30    );
31    let text_root = universe.world.add_component_boxed_named(
32        format!("{label}_text_root"),
33        Box::new(TransformComponent::new().with_position(0.0, 0.0, 0.2)),
34    );
35    let text = universe
36        .world
37        .add_component_boxed_named(format!("{label}_text"), Box::new(TextComponent::new(label)));
38    let color = universe
39        .world
40        .add_component(ColorComponent::rgba(0.40, 0.05, 0.05, 1.0));
41
42    let _ = universe.world.add_child(root, style);
43    let _ = universe.world.add_child(root, text_root);
44    let _ = universe.world.add_child(text_root, text);
45    let _ = universe.world.add_child(text, color);
46
47    universe.attach(owner, root).expect("attach routed child");
48}
49
50fn collect_container_items(
51    world: &engine::ecs::World,
52    container: engine::ecs::ComponentId,
53) -> Vec<engine::ecs::ComponentId> {
54    use engine::ecs::component::TransformComponent;
55
56    world
57        .children_of(container)
58        .iter()
59        .copied()
60        .filter(|&child| {
61            world
62                .get_component_by_id_as::<TransformComponent>(child)
63                .is_some()
64                && !world
65                    .component_label(child)
66                    .map(|label| label.starts_with("__"))
67                    .unwrap_or(false)
68        })
69        .collect()
70}
71
72fn decompose_world_trs(matrix_world: [[f32; 4]; 4]) -> ([f32; 3], [f32; 4], [f32; 3]) {
73    let translation = [matrix_world[3][0], matrix_world[3][1], matrix_world[3][2]];
74    let scale = [
75        (matrix_world[0][0] * matrix_world[0][0]
76            + matrix_world[0][1] * matrix_world[0][1]
77            + matrix_world[0][2] * matrix_world[0][2])
78            .sqrt(),
79        (matrix_world[1][0] * matrix_world[1][0]
80            + matrix_world[1][1] * matrix_world[1][1]
81            + matrix_world[1][2] * matrix_world[1][2])
82            .sqrt(),
83        (matrix_world[2][0] * matrix_world[2][0]
84            + matrix_world[2][1] * matrix_world[2][1]
85            + matrix_world[2][2] * matrix_world[2][2])
86            .sqrt(),
87    ];
88    let rotation = mittens_engine::utils::math::mat_to_quat(matrix_world);
89    (translation, rotation, scale)
90}
91
92fn spawn_container_item_debug_clones(
93    universe: &mut engine::Universe,
94    container: engine::ecs::ComponentId,
95) {
96    use engine::ecs::IntentValue;
97    use engine::ecs::system::TransformSystem;
98    use mittens_engine::scripting::component_registry::{
99        ce_ast_to_materialized, spawn_tree, subtree_to_ce_ast,
100    };
101
102    let items = collect_container_items(&universe.world, container);
103    for (index, item) in items.into_iter().enumerate() {
104        let Some(matrix_world) = TransformSystem::world_model(&universe.world, item) else {
105            println!("[diy-panel-debug] skip {:?}: no world matrix", item);
106            continue;
107        };
108
109        let item_label = universe
110            .world
111            .component_label(item)
112            .unwrap_or("")
113            .to_string();
114        println!(
115            "[diy-panel-debug] item={} label={:?} matrix_world={:?}",
116            index, item_label, matrix_world,
117        );
118
119        // Subtree → MMS AST → MaterializedCE → fresh spawn.
120        // Same path attach_clone uses; gives the clone fresh GUIDs and
121        // routes everything through the standard component registry.
122        let Ok(ce_ast) = subtree_to_ce_ast(&universe.world, item) else {
123            println!(
124                "[diy-panel-debug] skip {:?}: failed to encode subtree",
125                item
126            );
127            continue;
128        };
129        let Ok(materialized) = ce_ast_to_materialized(&ce_ast) else {
130            println!(
131                "[diy-panel-debug] skip {:?}: failed to materialize CE",
132                item
133            );
134            continue;
135        };
136        let Ok(clone_root) = spawn_tree(
137            &materialized,
138            None,
139            &mut universe.world,
140            &mut universe.command_queue,
141        ) else {
142            println!("[diy-panel-debug] skip {:?}: failed to spawn clone", item);
143            continue;
144        };
145
146        universe.add(clone_root);
147
148        let (mut clone_translation, clone_rotation, clone_scale) =
149            decompose_world_trs(matrix_world);
150        clone_translation[0] += DEBUG_CLONE_WORLD_OFFSET[0];
151        clone_translation[1] += DEBUG_CLONE_WORLD_OFFSET[1];
152        clone_translation[2] += DEBUG_CLONE_WORLD_OFFSET[2];
153
154        universe.command_queue.push_intent_now(
155            clone_root,
156            IntentValue::UpdateTransform {
157                component_ids: vec![clone_root],
158                translation: clone_translation,
159                rotation_quat_xyzw: clone_rotation,
160                scale: clone_scale,
161            },
162        );
163
164        let label = if item_label.is_empty() {
165            format!("item_{index}")
166        } else {
167            format!("{}_{}", item_label, index)
168        };
169        println!(
170            "[diy-panel-debug] cloned item={} label={:?} clone_root={:?} offset_translation={:?}",
171            index, label, clone_root, clone_translation,
172        );
173    }
174}
175
176fn main() {
177    mittens_engine::example_support::ensure_model_assets();
178    utils::logger::init();
179
180    let output = scripting::MeowMeowRunner::eval(include_str!("diy-panel.mms"));
181
182    for error in &output.errors {
183        eprintln!("[mms] {error}");
184    }
185    println!(
186        "[mms] {} intent(s) from diy-panel.mms",
187        output.intents.len()
188    );
189
190    if !output.errors.is_empty() {
191        std::process::exit(1);
192    }
193
194    let world = engine::ecs::World::default();
195    let mut universe = engine::Universe::new(world);
196
197    let scope = engine::ecs::ComponentId::default();
198    for intent in output.intents {
199        universe.command_queue.push_intent_now(scope, intent);
200    }
201
202    universe.systems.process_commands(
203        &mut universe.world,
204        &mut universe.visuals,
205        &mut universe.render_assets,
206        &mut universe.command_queue,
207    );
208
209    universe.enable_repl();
210
211    let demo_root = universe
212        .world
213        .all_components()
214        .find(|&cid| universe.world.component_label(cid) == Some("diy_panel_demo"))
215        .expect("diy panel demo root");
216    let container = universe
217        .find_component(demo_root, "[name='container']")
218        .expect("container target");
219    let authored_child = universe
220        .find_component(demo_root, "[name='authored_child']")
221        .expect("authored child");
222
223    assert_eq!(universe.parent_of(authored_child), Some(container));
224
225    spawn_runtime_text(&mut universe, demo_root, "late child routed on attach");
226
227    let late_child = universe
228        .find_component(demo_root, "[name='late child routed on attach']")
229        .expect("late child");
230    assert_eq!(universe.parent_of(late_child), Some(container));
231
232    let input = engine::user_input::InputState::default();
233    universe.systems.tick(
234        &mut universe.world,
235        &mut universe.visuals,
236        &mut universe.render_assets,
237        &input,
238        &mut universe.command_queue,
239        0.0,
240    );
241    universe.systems.process_commands(
242        &mut universe.world,
243        &mut universe.visuals,
244        &mut universe.render_assets,
245        &mut universe.command_queue,
246    );
247
248    //spawn_container_item_debug_clones(&mut universe, container);
249    universe.systems.process_commands(
250        &mut universe.world,
251        &mut universe.visuals,
252        &mut universe.render_assets,
253        &mut universe.command_queue,
254    );
255
256    let scroll = universe
257        .world
258        .children_of(container)
259        .iter()
260        .copied()
261        .find(|&child| {
262            universe.world.component_label(child) == Some("__scroll")
263                && universe
264                    .world
265                    .get_component_by_id_as::<engine::ecs::component::ScrollingComponent>(child)
266                    .is_some()
267        })
268        .expect("layout-owned scroll wrapper");
269    let track = universe
270        .world
271        .get_component_by_id_as::<engine::ecs::component::ScrollingComponent>(scroll)
272        .and_then(|sc| sc.track)
273        .expect("layout-owned scroll track");
274
275    assert_eq!(universe.parent_of(authored_child), Some(track));
276    assert_eq!(universe.parent_of(late_child), Some(track));
277
278    println!(
279        "[diy-panel] init-time routing verified, then layout-owned scrolling moved content into __scroll_track"
280    );
281
282    engine::Windowing::run_app(universe).expect("Windowing failed");
283}