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 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 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}