Skip to main content

scrolling/
scrolling.rs

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}