1use std::collections::HashSet;
5use web_time::Duration;
6
7use anyhow::Result;
8pub use repame_sim::{Sim, SimTime};
9pub use repame_sprite::{Camera2d, PickEvent, SpriteInstance};
10use repose_core::View;
11pub use repose_core::input::{GamepadEvent, GamepadId};
12use repose_platform::gamepad::{GamepadBackend, create_backend};
13
14mod gesture;
15mod haptics;
16mod levels;
17mod motion;
18mod pads;
19mod quit;
20mod shortcuts;
21mod staging;
22pub use gesture::{on_first_user_gesture, user_gesture_seen};
23pub use haptics::{RUMBLE_REFRESH, RUMBLE_UPLOAD_MS, RumbleBridge};
24pub use levels::apply_scheduler_levels;
25pub use motion::{MotionPoller, PadMotion};
26pub use pads::{PadBank, PadBridge, TRIGGER_HELD};
27pub use quit::Quit;
28pub use shortcuts::{
29 SharedEdges, ShortcutEdges, game_shortcut_map, install_game_shortcuts, install_handler_into,
30 install_map, shared_edges, shortcut_handler, take as take_shortcut_edges,
31};
32pub use staging::Staging;
33
34pub trait ShellHooks {
36 fn on_frame(&mut self, _sim: &mut Sim, _dt: Duration) {}
38}
39
40pub struct RetainedRoot {
41 key: String,
42 generation: u64,
43}
44
45impl RetainedRoot {
46 pub fn new() -> Self {
47 Self {
48 key: format!(
49 "repame.retained_root.{}",
50 repose_core::unique_component_id()
51 ),
52 generation: 0,
53 }
54 }
55
56 pub fn call<F>(&mut self, sched: &mut repose_core::runtime::Scheduler, build: F) -> View
57 where
58 F: FnOnce(&mut repose_core::runtime::Scheduler) -> View,
59 {
60 let key = &self.key;
61 let epoch = repose_core::timer::frame_count();
62 let generation = self.generation;
63 repose_core::scope!(key, sched, [epoch, generation], { build(sched) })
64 }
65
66 pub fn clear(&mut self) {
67 self.generation = self.generation.wrapping_add(1);
68 }
69}
70
71impl Default for RetainedRoot {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
84pub fn run_desktop(
85 title: &str,
86 size: (u32, u32),
87 mut root: impl FnMut(
88 &mut repose_core::runtime::Scheduler,
89 &repose_core::RenderContext,
90 ) -> repose_core::View
91 + 'static,
92) -> Result<()> {
93 let config = repose_app::AppConfig {
94 window_title: title.to_string(),
95 window_size: size,
96 ..Default::default()
97 };
98 let mut retained = RetainedRoot::new();
99 repose_platform::run_desktop_app_with_config(
100 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
101 config,
102 )?;
103 Ok(())
104}
105
106#[cfg(target_arch = "wasm32")]
108pub fn run_web(
109 mut root: impl FnMut(
110 &mut repose_core::runtime::Scheduler,
111 &repose_core::RenderContext,
112 ) -> repose_core::View
113 + 'static,
114) -> Result<(), wasm_bindgen::JsValue> {
115 let mut options = repose_platform::web::WebOptions::new(None);
116 options.set_prevent_default(true);
117 let mut retained = RetainedRoot::new();
118 repose_platform::web::run_web_app(
119 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
120 options,
121 )
122}
123
124#[cfg(target_os = "android")]
126pub fn run_android(
127 app: winit::platform::android::activity::AndroidApp,
128 mut root: impl FnMut(
129 &mut repose_core::runtime::Scheduler,
130 &repose_core::RenderContext,
131 ) -> repose_core::View
132 + 'static,
133) -> Result<()> {
134 let mut retained = RetainedRoot::new();
135 repose_platform::android::run_android_app_with_options(
136 app,
137 move |sched, ctx| retained.call(sched, |sched| root(sched, ctx)),
138 repose_app::AndroidOptions::default(),
139 )
140}
141
142pub struct GamepadPoller {
145 backend: Option<Box<dyn GamepadBackend>>,
146 connected: HashSet<u32>,
147}
148
149impl GamepadPoller {
150 pub fn new() -> Self {
151 Self {
152 backend: create_backend().map(|b| Box::new(b) as Box<dyn GamepadBackend>),
153 connected: HashSet::new(),
155 }
156 }
157
158 pub fn poll(&mut self) -> Vec<GamepadEvent> {
161 let Some(backend) = &mut self.backend else {
162 return Vec::new();
163 };
164 let events = backend.poll();
165 for ev in &events {
166 match ev {
167 GamepadEvent::Connected { id, .. } => {
168 self.connected.insert(id.0);
169 }
170 GamepadEvent::Disconnected { id } => {
171 self.connected.remove(&id.0);
172 }
173 GamepadEvent::Button { id, .. } | GamepadEvent::Axis { id, .. } => {
174 self.connected.insert(id.0);
175 }
176 }
177 }
178 events
179 }
180
181 pub fn connected_ids(&self) -> Vec<GamepadId> {
182 let mut ids: Vec<GamepadId> = self.connected.iter().copied().map(GamepadId).collect();
183 ids.sort_by_key(|id| id.0);
184 ids
185 }
186
187 pub fn connected_count(&self) -> usize {
188 self.connected.len()
189 }
190}
191
192impl Default for GamepadPoller {
193 fn default() -> Self {
194 Self::new()
195 }
196}