1use mittens_engine::{engine, utils};
12
13#[path = "example_util/mod.rs"]
14mod example_util;
15
16use std::sync::{Mutex, OnceLock};
17
18#[derive(Debug)]
23struct ClickCubeState {
24 color_node: engine::ecs::ComponentId,
25 click_count: u32,
26}
27
28static CLICK_CUBES: OnceLock<
29 Mutex<std::collections::HashMap<engine::ecs::ComponentId, ClickCubeState>>,
30> = OnceLock::new();
31
32fn click_cube_colors() -> &'static [[f32; 4]] {
33 &[
34 [0.25, 0.55, 1.00, 1.0], [1.00, 0.35, 0.35, 1.0], [0.30, 0.85, 0.45, 1.0], [1.00, 0.80, 0.15, 1.0], [0.75, 0.30, 1.00, 1.0], ]
40}
41
42fn on_click_cube(
43 world: &mut engine::ecs::World,
44 emit: &mut dyn engine::ecs::SignalEmitter,
45 env: &engine::ecs::Signal,
46) {
47 if !matches!(
48 env.event.as_ref(),
49 Some(engine::ecs::EventSignal::Click { .. })
50 ) {
51 return;
52 }
53
54 let map = CLICK_CUBES.get_or_init(|| Mutex::new(Default::default()));
56 let mut guard = map.lock().unwrap();
57 let mut cur = Some(env.scope);
58 let mut found_root = None;
59 while let Some(id) = cur {
60 if guard.contains_key(&id) {
61 found_root = Some(id);
62 break;
63 }
64 cur = world.parent_of(id);
65 }
66 let Some(root) = found_root else { return };
67 let Some(state) = guard.get_mut(&root) else {
68 return;
69 };
70
71 state.click_count += 1;
72 let colors = click_cube_colors();
73 let rgba = colors[state.click_count as usize % colors.len()];
74
75 emit.push_intent_now(
76 root,
77 engine::ecs::IntentValue::SetColor {
78 component_ids: vec![state.color_node],
79 rgba,
80 },
81 );
82}
83
84fn spawn_click_cube(
85 universe: &mut engine::Universe,
86 parent: engine::ecs::ComponentId,
87 pos: [f32; 3],
88) -> engine::ecs::ComponentId {
89 use engine::ecs::component::{
90 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
91 };
92
93 let root = universe.world.add_component(
94 TransformComponent::new()
95 .with_position(pos[0], pos[1], pos[2])
96 .with_scale(0.40, 0.40, 0.40),
97 );
98 let color_node = universe
99 .world
100 .add_component(ColorComponent::rgba(0.25, 0.55, 1.0, 1.0));
101 let r = universe.world.add_component(RenderableComponent::cube());
102 let rc = universe
103 .world
104 .add_component(RaycastableComponent::enabled());
105
106 let _ = universe.attach(root, color_node);
110 let _ = universe.attach(color_node, r);
111 let _ = universe.attach(r, rc);
112 let _ = universe.attach(parent, root);
113
114 CLICK_CUBES
115 .get_or_init(|| Mutex::new(Default::default()))
116 .lock()
117 .unwrap()
118 .insert(
119 root,
120 ClickCubeState {
121 color_node,
122 click_count: 0,
123 },
124 );
125
126 universe.add_signal_handler(engine::ecs::SignalKind::Click, root, on_click_cube);
127
128 root
129}
130
131fn on_drag_move(
136 world: &mut engine::ecs::World,
137 emit: &mut dyn engine::ecs::SignalEmitter,
138 env: &engine::ecs::Signal,
139) {
140 let Some(engine::ecs::EventSignal::DragMove { delta_world, .. }) = env.event.as_ref() else {
141 return;
142 };
143 let delta = *delta_world;
144
145 let mut cur = Some(env.scope);
147 while let Some(id) = cur {
148 if world
149 .get_component_by_id_as::<engine::ecs::component::TransformComponent>(id)
150 .is_some()
151 {
152 let [x, y, z] = world
153 .get_component_by_id_as::<engine::ecs::component::TransformComponent>(id)
154 .map(|t| t.transform.translation)
155 .unwrap_or([0.0; 3]);
156
157 emit.push_intent_now(
158 id,
159 engine::ecs::IntentValue::SetPosition {
160 component_ids: vec![id],
161 position: [x + delta[0], y + delta[1], z + delta[2]],
162 },
163 );
164 return;
165 }
166 cur = world.parent_of(id);
167 }
168}
169
170fn spawn_drag_cube(
171 universe: &mut engine::Universe,
172 parent: engine::ecs::ComponentId,
173 pos: [f32; 3],
174 color: [f32; 4],
175) -> engine::ecs::ComponentId {
176 use engine::ecs::component::{
177 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
178 };
179
180 let root = universe.world.add_component(
181 TransformComponent::new()
182 .with_position(pos[0], pos[1], pos[2])
183 .with_scale(0.45, 0.45, 0.45),
184 );
185 let color_node = universe
186 .world
187 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
188 let r = universe.world.add_component(RenderableComponent::cube());
189 let rc = universe
190 .world
191 .add_component(RaycastableComponent::enabled());
192
193 let _ = universe.attach(root, color_node);
194 let _ = universe.attach(color_node, r);
195 let _ = universe.attach(r, rc);
196 let _ = universe.attach(parent, root);
197
198 universe.add_signal_handler(engine::ecs::SignalKind::DragMove, r, on_drag_move);
199
200 root
201}
202
203#[derive(Debug)]
208struct BothCubeState {
209 color_node: engine::ecs::ComponentId,
210 click_count: u32,
211}
212
213static BOTH_CUBES: OnceLock<
214 Mutex<std::collections::HashMap<engine::ecs::ComponentId, BothCubeState>>,
215> = OnceLock::new();
216
217fn on_both_cube(
218 world: &mut engine::ecs::World,
219 emit: &mut dyn engine::ecs::SignalEmitter,
220 env: &engine::ecs::Signal,
221) {
222 match env.event.as_ref() {
223 Some(engine::ecs::EventSignal::DragMove { delta_world, .. }) => {
224 on_drag_move(world, emit, env);
225 let _ = delta_world;
226 }
227 Some(engine::ecs::EventSignal::Click { .. }) => {
228 let map = BOTH_CUBES.get_or_init(|| Mutex::new(Default::default()));
229 let mut guard = map.lock().unwrap();
230 let mut cur = Some(env.scope);
232 let mut found_root = None;
233 while let Some(id) = cur {
234 if guard.contains_key(&id) {
235 found_root = Some(id);
236 break;
237 }
238 cur = world.parent_of(id);
239 }
240 let Some(root) = found_root else { return };
241 let Some(state) = guard.get_mut(&root) else {
242 return;
243 };
244
245 state.click_count += 1;
246 let colors = click_cube_colors();
247 let rgba = colors[state.click_count as usize % colors.len()];
248 emit.push_intent_now(
249 root,
250 engine::ecs::IntentValue::SetColor {
251 component_ids: vec![state.color_node],
252 rgba,
253 },
254 );
255 }
256 _ => {}
257 }
258}
259
260fn spawn_both_cube(
261 universe: &mut engine::Universe,
262 parent: engine::ecs::ComponentId,
263 pos: [f32; 3],
264) -> engine::ecs::ComponentId {
265 use engine::ecs::component::{
266 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
267 };
268
269 let root = universe.world.add_component(
270 TransformComponent::new()
271 .with_position(pos[0], pos[1], pos[2])
272 .with_scale(0.45, 0.45, 0.45),
273 );
274 let color_node = universe
275 .world
276 .add_component(ColorComponent::rgba(0.95, 0.60, 0.20, 1.0));
277 let r = universe.world.add_component(RenderableComponent::cube());
278 let rc = universe
279 .world
280 .add_component(RaycastableComponent::enabled());
281
282 let _ = universe.attach(root, color_node);
283 let _ = universe.attach(color_node, r);
284 let _ = universe.attach(r, rc);
285 let _ = universe.attach(parent, root);
286
287 BOTH_CUBES
288 .get_or_init(|| Mutex::new(Default::default()))
289 .lock()
290 .unwrap()
291 .insert(
292 root,
293 BothCubeState {
294 color_node,
295 click_count: 0,
296 },
297 );
298
299 universe.add_signal_handler(engine::ecs::SignalKind::DragMove, r, on_both_cube);
300 universe.add_signal_handler(engine::ecs::SignalKind::Click, r, on_both_cube);
301
302 root
303}
304
305fn spawn_label(
310 universe: &mut engine::Universe,
311 parent: engine::ecs::ComponentId,
312 pos: [f32; 3],
313 text: &str,
314) {
315 use engine::ecs::component::{
316 ColorComponent, EmissiveComponent, TextComponent, TextureFilteringComponent,
317 TransformComponent, TransparentCutoutComponent,
318 };
319
320 let root = universe.world.add_component(
321 TransformComponent::new()
322 .with_position(pos[0], pos[1], pos[2])
323 .with_scale(0.055, 0.055, 1.0),
324 );
325 let txt = universe.world.add_component(TextComponent::new(text));
326 let color = universe
327 .world
328 .add_component(ColorComponent::rgba(0.05, 0.05, 0.08, 1.0));
329 let emissive = universe.world.add_component(EmissiveComponent::on());
330 let filtering = universe
331 .world
332 .add_component(TextureFilteringComponent::nearest_magnification());
333 let cutout = universe
334 .world
335 .add_component(TransparentCutoutComponent::new());
336
337 let _ = universe.attach(parent, root);
338 let _ = universe.attach(root, txt);
339 let _ = universe.attach(txt, color);
340 let _ = universe.attach(txt, emissive);
341 let _ = universe.attach(txt, filtering);
342 let _ = universe.attach(txt, cutout);
343}
344
345fn main() {
350 mittens_engine::example_support::ensure_model_assets();
351 utils::logger::init();
352
353 let world = engine::ecs::World::default();
354 let mut universe = engine::Universe::new(world);
355
356 use engine::ecs::component::{
357 AmbientLightComponent, BackgroundColorComponent, Camera3DComponent, ColorComponent,
358 DirectionalLightComponent, InputComponent, InputTransformModeComponent, PointerComponent,
359 TransformComponent,
360 };
361
362 let bg = universe
364 .world
365 .add_component(BackgroundColorComponent::new());
366 let bg_c = universe
367 .world
368 .add_component(ColorComponent::rgba(0.12, 0.12, 0.16, 1.0));
369 let _ = universe.world.add_child(bg, bg_c);
370 universe.add(bg);
371
372 let ambient = universe
374 .world
375 .add_component(AmbientLightComponent::rgb(0.30, 0.30, 0.32));
376 universe.add(ambient);
377
378 let sun_t = universe
379 .world
380 .add_component(TransformComponent::new().with_position(2.0, 4.0, 3.0));
381 let sun = universe
382 .world
383 .add_component(DirectionalLightComponent::new());
384 let _ = universe.attach(sun_t, sun);
385 universe.add(sun_t);
386
387 let input = universe
389 .world
390 .add_component(InputComponent::new().with_speed(3.0));
391 let input_mode = universe.world.add_component(
392 InputTransformModeComponent::forward_z()
393 .with_fps_rotation()
394 .with_roll_axis_y(),
395 );
396 let _ = universe.attach(input, input_mode);
397
398 let cam_t = universe
399 .world
400 .add_component(TransformComponent::new().with_position(0.0, 0.5, 4.5));
401 let cam = universe
402 .world
403 .add_component(Camera3DComponent::new().with_fov(70.0));
404 let pointer = universe.world.add_component(PointerComponent::new());
405
406 let _ = universe.attach(input, cam_t);
407 let _ = universe.attach(cam_t, cam);
408 let _ = universe.attach(cam, pointer);
409
410 example_util::spawn_desktop_camera_controls_hint(&mut universe, cam_t);
411 universe.add(input);
412
413 let scene = universe.world.add_component(TransformComponent::new());
415 universe.add(scene);
416
417 let click_group = universe
419 .world
420 .add_component(TransformComponent::new().with_position(-2.2, 0.0, 0.0));
421 let _ = universe.attach(scene, click_group);
422
423 for (i, dy) in [-0.55_f32, 0.0, 0.55].iter().enumerate() {
424 spawn_click_cube(&mut universe, click_group, [0.0, *dy, 0.0]);
425 let _ = i;
426 }
427 spawn_label(
428 &mut universe,
429 click_group,
430 [-0.25, -1.05, 0.0],
431 "click\ncycles color",
432 );
433
434 let drag_cube_root = universe
436 .world
437 .add_component(TransformComponent::new().with_position(0.0, 0.0, 0.0));
438 let _ = universe.attach(scene, drag_cube_root);
439
440 spawn_drag_cube(
441 &mut universe,
442 drag_cube_root,
443 [0.0, 0.0, 0.0],
444 [0.35, 0.85, 0.55, 1.0],
445 );
446 spawn_label(
447 &mut universe,
448 drag_cube_root,
449 [-0.25, -0.80, 0.0],
450 "drag\nto move",
451 );
452
453 let both_root = universe
455 .world
456 .add_component(TransformComponent::new().with_position(2.2, 0.0, 0.0));
457 let _ = universe.attach(scene, both_root);
458
459 spawn_both_cube(&mut universe, both_root, [0.0, 0.0, 0.0]);
460 spawn_label(
461 &mut universe,
462 both_root,
463 [-0.30, -0.80, 0.0],
464 "drag to move\nclick for color",
465 );
466
467 universe.systems.process_commands(
468 &mut universe.world,
469 &mut universe.visuals,
470 &mut universe.render_assets,
471 &mut universe.command_queue,
472 );
473
474 universe.enable_repl();
475 engine::Windowing::run_app(universe).expect("Windowing failed");
476}