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