1use mittens_engine::{engine, engine::ecs::SignalEmitter, scripting, utils};
2
3fn find_named(world: &engine::ecs::World, label: &str) -> engine::ecs::ComponentId {
4 world
5 .all_components()
6 .find(|&cid| world.component_label(cid) == Some(label))
7 .unwrap_or_else(|| panic!("missing component {label:?}"))
8}
9
10fn first_renderable_in_subtree(
11 world: &engine::ecs::World,
12 root: engine::ecs::ComponentId,
13) -> engine::ecs::ComponentId {
14 let mut stack = vec![root];
15 while let Some(node) = stack.pop() {
16 if world
17 .get_component_by_id_as::<engine::ecs::component::RenderableComponent>(node)
18 .is_some()
19 {
20 return node;
21 }
22 for &child in world.children_of(node).iter().rev() {
23 stack.push(child);
24 }
25 }
26
27 panic!("missing renderable under subtree {:?}", root);
28}
29
30fn spawn_scroll_item(
31 universe: &mut engine::Universe,
32 scrolling: engine::ecs::ComponentId,
33 prefix: &str,
34 index: usize,
35 panel_rgba: [f32; 4],
36 text_rgba: [f32; 4],
37) -> engine::ecs::ComponentId {
38 use engine::ecs::component::{
39 ColorComponent, RenderableComponent, TextComponent, TransformComponent,
40 };
41
42 let label = format!("{prefix}_item_{index:02}");
43 let root = universe.world.add_component_boxed_named(
44 label.clone(),
45 Box::new(TransformComponent::new().with_position(0.0, -(index as f32) * 1.15, 0.05)),
46 );
47 let panel = universe.world.add_component_boxed_named(
48 format!("{label}_panel"),
49 Box::new(
50 TransformComponent::new()
51 .with_position(2.3, -0.45, 0.0)
52 .with_scale(4.6, 0.9, 1.0),
53 ),
54 );
55 let panel_renderable = universe.world.add_component_boxed_named(
56 format!("{label}_renderable"),
57 Box::new(RenderableComponent::square()),
58 );
59 let panel_color = universe.world.add_component(ColorComponent::rgba(
60 panel_rgba[0],
61 panel_rgba[1],
62 panel_rgba[2],
63 panel_rgba[3],
64 ));
65
66 let text_anchor = universe.world.add_component_boxed_named(
67 format!("{label}_text_anchor"),
68 Box::new(
69 TransformComponent::new()
70 .with_position(0.35, -0.38, 0.02)
71 .with_scale(0.11, 0.11, 0.11),
72 ),
73 );
74 let text = universe.world.add_component_boxed_named(
75 format!("{label}_text"),
76 Box::new(TextComponent::new(label.clone())),
77 );
78 let text_color = universe.world.add_component(ColorComponent::rgba(
79 text_rgba[0],
80 text_rgba[1],
81 text_rgba[2],
82 text_rgba[3],
83 ));
84
85 let _ = universe.world.add_child(root, panel);
86 let _ = universe.world.add_child(panel, panel_renderable);
87 let _ = universe.world.add_child(panel_renderable, panel_color);
88 let _ = universe.world.add_child(root, text_anchor);
89 let _ = universe.world.add_child(text_anchor, text);
90 let _ = universe.world.add_child(text, text_color);
91
92 universe
93 .attach(scrolling, root)
94 .expect("attach scroll item");
95 root
96}
97
98fn populate_scrolling(
99 universe: &mut engine::Universe,
100 scrolling: engine::ecs::ComponentId,
101 prefix: &str,
102 panel_rgba: [f32; 4],
103 text_rgba: [f32; 4],
104 count: usize,
105) {
106 for index in 0..count {
107 let root = spawn_scroll_item(universe, scrolling, prefix, index, panel_rgba, text_rgba);
108 let track = universe
109 .world
110 .get_component_by_id_as::<engine::ecs::component::ScrollingComponent>(scrolling)
111 .and_then(|sc| sc.track)
112 .expect("owned scroll track");
113 assert_eq!(universe.parent_of(root), Some(track));
114 }
115}
116
117fn main() {
118 mittens_engine::example_support::ensure_model_assets();
119 utils::logger::init();
120
121 let output = scripting::MeowMeowRunner::eval(include_str!("scrolling.mms"));
122
123 for error in &output.errors {
124 eprintln!("[mms] {error}");
125 }
126 println!(
127 "[mms] {} intent(s) from scrolling.mms",
128 output.intents.len()
129 );
130
131 if !output.errors.is_empty() {
132 std::process::exit(1);
133 }
134
135 let world = engine::ecs::World::default();
136 let mut universe = engine::Universe::new(world);
137
138 let scope = engine::ecs::ComponentId::default();
139 for intent in output.intents {
140 universe.command_queue.push_intent_now(scope, intent);
141 }
142
143 universe.systems.process_commands(
144 &mut universe.world,
145 &mut universe.visuals,
146 &mut universe.render_assets,
147 &mut universe.command_queue,
148 );
149
150 let manual_scroll = find_named(&universe.world, "manual_scroll");
151 let layout_scroll = find_named(&universe.world, "layout_scroll");
152 let layout_bg = find_named(&universe.world, "__bg");
153
154 let layout_bg_renderable = first_renderable_in_subtree(&universe.world, layout_bg);
155
156 let manual_drag_scope = universe
157 .world
158 .get_component_by_id_as::<engine::ecs::component::ScrollingComponent>(manual_scroll)
159 .and_then(|sc| sc.drag_scope)
160 .expect("manual drag scope");
161 let layout_drag_scope = universe
162 .world
163 .get_component_by_id_as::<engine::ecs::component::ScrollingComponent>(layout_scroll)
164 .and_then(|sc| sc.drag_scope)
165 .expect("layout drag scope");
166
167 assert_ne!(manual_drag_scope, layout_bg_renderable);
168 assert_eq!(layout_drag_scope, layout_bg_renderable);
169
170 populate_scrolling(
171 &mut universe,
172 manual_scroll,
173 "manual",
174 [0.78, 0.87, 0.98, 1.0],
175 [0.08, 0.16, 0.28, 1.0],
176 18,
177 );
178 populate_scrolling(
179 &mut universe,
180 layout_scroll,
181 "layout",
182 [0.98, 0.87, 0.74, 1.0],
183 [0.30, 0.14, 0.05, 1.0],
184 18,
185 );
186
187 println!(
188 "[scrolling-demo] manual drag_scope={:?} layout drag_scope={:?} late attach routing verified",
189 manual_drag_scope, layout_drag_scope
190 );
191
192 universe.enable_repl();
193 engine::Windowing::run_app(universe).expect("Windowing failed");
194}