1#![allow(clippy::type_complexity)]
2
3use crate::assets::{AssetLoader, Assets};
4use crate::config::*;
5use crate::graphics::Graphics;
6use crate::handlers::{
7 AppCallback, AppHandler, DrawCallback, DrawHandler, EventCallback, EventHandler,
8 ExtensionHandler, InitCallback, InitHandler, PluginHandler, SetupCallback,
9};
10use crate::parsers::*;
11use crate::plugins::*;
12use crate::{App, Backend, BackendSystem, FrameState, GfxExtension, GfxRenderer};
13use indexmap::IndexMap;
14#[cfg(feature = "audio")]
15use notan_audio::Audio;
16use notan_core::events::{Event, EventIterator};
17use notan_core::mouse::MouseButton;
18use notan_input::internals::{
19 clear_keyboard, clear_mouse, process_keyboard_events, process_mouse_events,
20 process_touch_events,
21};
22
23pub use crate::handlers::SetupHandler;
24
25pub trait BuildConfig<S, B>
27where
28 B: Backend,
29{
30 fn apply(&self, builder: AppBuilder<S, B>) -> AppBuilder<S, B>;
32
33 fn late_evaluation(&self) -> bool {
35 false
36 }
37}
38
39pub struct AppBuilder<S, B> {
41 setup_callback: SetupCallback<S>,
42 backend: B,
43
44 plugins: Plugins,
45 assets: Assets,
46
47 init_callback: Option<InitCallback<S>>,
48 update_callback: Option<AppCallback<S>>,
49 draw_callback: Option<DrawCallback<S>>,
50 event_callback: Option<EventCallback<S>>,
51
52 plugin_callbacks: Vec<Box<dyn FnOnce(&mut App, &mut Assets, &mut Graphics, &mut Plugins)>>,
53 extension_callbacks: Vec<Box<dyn FnOnce(&mut App, &mut Assets, &mut Graphics, &mut Plugins)>>,
54
55 late_config: Option<IndexMap<std::any::TypeId, Box<dyn BuildConfig<S, B>>>>,
56
57 use_touch_as_mouse: bool,
58
59 pub(crate) window: WindowConfig,
60}
61
62impl<S, B> AppBuilder<S, B>
63where
64 S: 'static,
65 B: BackendSystem + 'static,
66{
67 pub fn new<H, Params>(setup: H, backend: B) -> Self
69 where
70 H: SetupHandler<S, Params>,
71 {
72 let builder = AppBuilder {
73 backend,
74 plugins: Default::default(),
75 assets: Assets::new(),
76 setup_callback: setup.callback(),
77 init_callback: None,
78 update_callback: None,
79 draw_callback: None,
80 event_callback: None,
81 plugin_callbacks: vec![],
82 extension_callbacks: vec![],
83 window: Default::default(),
84 late_config: Some(Default::default()),
85 use_touch_as_mouse: true,
86 };
87
88 builder.default_loaders()
89 }
90
91 #[allow(unreachable_code)]
92 fn default_loaders(self) -> Self {
93 #[cfg(feature = "audio")]
94 {
95 self.add_loader(create_texture_parser())
96 .add_loader(create_audio_parser())
97 }
98
99 #[cfg(not(feature = "audio"))]
100 {
101 self.add_loader(create_texture_parser())
102 }
103 }
104
105 pub fn touch_as_mouse(mut self, enabled: bool) -> Self {
107 self.use_touch_as_mouse = enabled;
108 self
109 }
110
111 pub fn add_config<C>(mut self, config: C) -> Self
113 where
114 C: BuildConfig<S, B> + 'static,
115 {
116 if config.late_evaluation() {
117 if let Some(late_config) = &mut self.late_config {
118 let typ = std::any::TypeId::of::<C>();
119 late_config.insert(typ, Box::new(config));
120 }
121
122 self
123 } else {
124 config.apply(self)
125 }
126 }
127
128 pub fn initialize<H, Params>(mut self, handler: H) -> Self
130 where
131 H: InitHandler<S, Params>,
132 {
133 self.init_callback = Some(handler.callback());
134 self
135 }
136
137 pub fn update<H, Params>(mut self, handler: H) -> Self
139 where
140 H: AppHandler<S, Params>,
141 {
142 self.update_callback = Some(handler.callback());
143 self
144 }
145
146 pub fn draw<H, Params>(mut self, handler: H) -> Self
148 where
149 H: DrawHandler<S, Params>,
150 {
151 self.draw_callback = Some(handler.callback());
152 self
153 }
154
155 pub fn event<H, Params>(mut self, handler: H) -> Self
157 where
158 H: EventHandler<S, Params>,
159 {
160 self.event_callback = Some(handler.callback());
161 self
162 }
163
164 pub fn add_plugin<P: Plugin + 'static>(mut self, mut plugin: P) -> Self {
166 plugin.build(&mut self);
167 self.plugins.add(plugin);
168 self
169 }
170
171 pub fn add_plugin_with<P, H, Params>(mut self, handler: H) -> Self
173 where
174 P: Plugin + 'static,
175 H: PluginHandler<P, Params> + 'static,
176 {
177 let cb =
178 move |app: &mut App, assets: &mut Assets, gfx: &mut Graphics, plugins: &mut Plugins| {
179 let p = handler.callback().exec(app, assets, gfx, plugins);
180 plugins.add(p);
181 };
182 self.plugin_callbacks.push(Box::new(cb));
183 self
184 }
185
186 pub fn add_graphic_ext<R, E, H, Params>(mut self, handler: H) -> Self
188 where
189 R: GfxRenderer,
190 E: GfxExtension<R> + 'static,
191 H: ExtensionHandler<R, E, Params> + 'static,
192 {
193 let cb =
194 move |app: &mut App, assets: &mut Assets, gfx: &mut Graphics, plugins: &mut Plugins| {
195 let e = handler.callback().exec(app, assets, gfx, plugins);
196 gfx.add_extension(e);
197 };
198 self.extension_callbacks.push(Box::new(cb));
199 self
200 }
201
202 pub fn add_loader(mut self, loader: AssetLoader) -> Self {
204 self.assets.add_loader(loader);
205 self
206 }
207
208 pub fn build(self) -> Result<(), String> {
210 let mut builder = self;
211 if let Some(late_config) = builder.late_config.take() {
212 for (_, config) in late_config {
213 builder = config.apply(builder);
214 }
215 }
216
217 let AppBuilder {
218 mut backend,
219 setup_callback,
220 mut plugins,
221 mut assets,
222
223 init_callback,
224 update_callback,
225 draw_callback,
226 event_callback,
227 mut plugin_callbacks,
228 mut extension_callbacks,
229 window,
230 use_touch_as_mouse,
231 ..
232 } = builder;
233
234 let initialize = backend.initialize(window)?;
235
236 let mut graphics = Graphics::new(backend.get_graphics_backend())?;
237
238 #[cfg(feature = "audio")]
239 let audio = Audio::new(backend.get_audio_backend())?;
240 #[cfg(feature = "audio")]
241 let mut app = App::new(Box::new(backend), audio);
242
243 #[cfg(not(feature = "audio"))]
244 let mut app = App::new(Box::new(backend));
245
246 app.window().set_touch_as_mouse(use_touch_as_mouse);
247
248 let (width, height) = app.window().size();
249 let win_dpi = app.window().dpi();
250 graphics.set_size(width, height);
251 graphics.set_dpi(win_dpi);
252
253 extension_callbacks.reverse();
255 while let Some(cb) = extension_callbacks.pop() {
256 cb(&mut app, &mut assets, &mut graphics, &mut plugins);
257 }
258
259 plugin_callbacks.reverse();
261 while let Some(cb) = plugin_callbacks.pop() {
262 cb(&mut app, &mut assets, &mut graphics, &mut plugins);
263 }
264
265 let mut state = setup_callback.exec(&mut app, &mut assets, &mut graphics, &mut plugins);
267
268 let _ = plugins.init(&mut app, &mut assets, &mut graphics).map(|flow| match flow {
270 AppFlow::Next => Ok(()),
271 _ => Err(format!(
272 "Aborted application loop because a plugin returns on the init method AppFlow::{flow:?} instead of AppFlow::Next",
273 )),
274 })?;
275
276 if let Some(cb) = init_callback {
278 cb.exec(&mut app, &mut assets, &mut plugins, &mut state);
279 }
280
281 let mut current_touch_id: Option<u64> = None;
282
283 let mut first_loop = true;
284 if let Err(e) = initialize(app, state, move |app, mut state| {
285 app.system_timer.update();
287
288 let win_size = app.window().size();
289 if graphics.size() != win_size {
290 let (width, height) = win_size;
291 graphics.set_size(width, height);
292 }
293
294 let win_dpi = app.window().dpi();
295 if (graphics.dpi() - win_dpi).abs() > f64::EPSILON {
296 graphics.set_dpi(win_dpi);
297 }
298
299 if let AppFlow::SkipFrame = plugins.pre_frame(app, &mut assets, &mut graphics)? {
301 return Ok(FrameState::Skip);
302 }
303
304 app.timer.update();
306
307 assets.tick((app, &mut graphics, &mut plugins, &mut state))?;
308
309 let delta = app.timer.delta_f32();
310
311 let use_touch_as_mouse = app.window().touch_as_mouse();
312
313 let mut events = app.backend.events_iter();
315 while let Some(evt) = events.next() {
316 if use_touch_as_mouse {
317 touch_as_mouse(&mut current_touch_id, &mut events, &evt);
318 }
319
320 process_keyboard_events(&mut app.keyboard, &evt, delta);
321 process_mouse_events(&mut app.mouse, &evt, delta);
322 process_touch_events(&mut app.touch, &evt, delta);
323
324 match plugins.event(app, &mut assets, &evt)? {
325 AppFlow::Skip => {}
326 AppFlow::Next => {
327 if let Some(cb) = &event_callback {
328 cb.exec(app, &mut assets, &mut plugins, state, evt);
329 }
330 }
331 AppFlow::SkipFrame => return Ok(FrameState::Skip),
332 }
333 }
334
335 match plugins.update(app, &mut assets)? {
337 AppFlow::Skip => {}
338 AppFlow::Next => {
339 if let Some(cb) = &update_callback {
340 cb.exec(app, &mut assets, &mut plugins, state);
341 }
342 }
343 AppFlow::SkipFrame => return Ok(FrameState::Skip),
344 }
345
346 match plugins.draw(app, &mut assets, &mut graphics)? {
348 AppFlow::Skip => {}
349 AppFlow::Next => {
350 if let Some(cb) = &draw_callback {
351 cb.exec(app, &mut assets, &mut graphics, &mut plugins, state);
352 }
353 }
354 AppFlow::SkipFrame => return Ok(FrameState::Skip),
355 }
356
357 if app.window().lazy_loop() {
359 let mouse_down = !app.mouse.down.is_empty();
360 let key_down = !app.keyboard.down.is_empty();
361 if mouse_down || key_down {
362 app.window().request_frame();
363 }
364 }
365
366 clear_mouse(&mut app.mouse);
367 clear_keyboard(&mut app.keyboard);
368
369 let _ = plugins.post_frame(app, &mut assets, &mut graphics)?;
371
372 graphics.clean();
374 #[cfg(feature = "audio")]
375 app.audio.clean();
376
377 if app.closed {
379 let evt = Event::Exit;
380 let _ = plugins.event(app, &mut assets, &evt)?;
381 if let Some(cb) = &event_callback {
382 cb.exec(app, &mut assets, &mut plugins, state, evt);
383 }
384 }
385
386 if !app.closed && app.window().lazy_loop() && first_loop {
389 first_loop = false;
390 app.window().request_frame();
391 }
392
393 Ok(FrameState::End)
394 }) {
395 log::error!("{}", e);
396 }
397
398 Ok(())
399 }
400}
401
402#[inline]
403fn touch_as_mouse(current_touch_id: &mut Option<u64>, events: &mut EventIterator, evt: &Event) {
404 match evt {
405 Event::TouchStart { id, x, y } => {
406 if current_touch_id.is_none() || current_touch_id.unwrap() == *id {
407 *current_touch_id = Some(*id);
408 events.push_front(Event::MouseDown {
409 button: MouseButton::Left,
410 x: *x as _,
411 y: *y as _,
412 });
413 }
414 }
415 Event::TouchMove { id, x, y } => {
416 if let Some(last_id) = current_touch_id {
417 if last_id == id {
418 events.push_front(Event::MouseMove {
419 x: *x as _,
420 y: *y as _,
421 });
422 }
423 }
424 }
425 Event::TouchEnd { id, x, y } => {
426 if let Some(last_id) = current_touch_id {
427 if last_id == id {
428 *current_touch_id = None;
429 events.push_front(Event::MouseUp {
430 button: MouseButton::Left,
431 x: *x as _,
432 y: *y as _,
433 });
434 }
435 }
436 }
437 Event::TouchCancel { id, x, y } => {
438 if let Some(last_id) = current_touch_id {
439 if last_id == id {
440 *current_touch_id = None;
441 events.push_front(Event::MouseUp {
442 button: MouseButton::Left,
443 x: *x as _,
444 y: *y as _,
445 });
446 }
447 }
448 }
449 _ => {}
450 }
451}