1use glyphon::{Attrs, Metrics, Shaping};
7
8use image::ImageError;
9use spin_sleep::SpinSleeper;
10use std::num::NonZeroU64;
11use std::path::Path;
12use web_time::Instant;
13use wgpu::{CreateSurfaceError, RequestDeviceError};
14use winit::application::ApplicationHandler;
15use winit::error::OsError;
16use winit::event::*;
17use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
18use winit::window::BadIcon;
19
20use crate::context::{GraphicsContext, WindowOptions};
21use crate::input::{InputHandle, Key, ModifierKeys, MouseKey};
22use crate::render::render;
23use crate::resource;
24use crate::resource::{
25 InProgressResource, Loader, LoadingOp, Resource, ResourceError, ResourceId, ResourceManager,
26 ResourceType,
27};
28use crate::shader::{FinalShaderOptions, IntermediateOptions, Shader};
29use crate::text::Font;
30use crate::texture::{SamplerType, Texture};
31use crate::vectors::Vec2;
32use crate::Game;
33
34pub struct Engine {
36 input_handle: InputHandle,
37 event_loop: Option<EventLoop<BpEvent>>,
38 window_options: Option<WindowOptions>,
39 proxy: EventLoopProxy<BpEvent>,
40 cursor_visibility: bool,
41 should_close: bool,
42 close_key: Option<Key>,
43 target_fps: Option<u16>,
44 last_frame: Instant,
45 spin_sleeper: SpinSleeper,
46 current_frametime: Instant,
47 size: Vec2<u32>,
48 pub(crate) context: Option<GraphicsContext>,
49 pub(crate) resource_manager: ResourceManager,
50 pub(crate) loader: Loader,
51 pub(crate) defualt_resources: DefualtResources,
52 ma_frame_time: f32,
53}
54
55impl Engine {
56 fn new(builder: EngineBuilder) -> Result<Self, BuildError> {
57 cfg_if::cfg_if! {
58 if #[cfg(target_arch = "wasm32")] {
59 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
60 console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
61 } else {
62 env_logger::init();
63 }
64 }
65
66 let cursor_visibility = true;
67 let input_handle = InputHandle::new();
68 let size: Vec2<u32> = builder.resolution.into();
69 let target_fps = builder.target_fps;
70
71 let event_loop: EventLoop<BpEvent> = EventLoop::with_user_event().build().unwrap();
72 let proxy = event_loop.create_proxy();
73
74 let resource_manager = ResourceManager::new();
75
76 let close_key = builder.close_key;
77
78 let line_id = resource::generate_id::<Shader>();
79 let generic_id = resource::generate_id::<Shader>();
80 let white_pixel_id = resource::generate_id::<Texture>();
81
82 let defualt_resources = DefualtResources {
83 default_pipeline_id: generic_id,
84 defualt_texture_id: white_pixel_id,
85 line_pipeline_id: line_id,
86 };
87
88 Ok(Self {
89 input_handle,
90 event_loop: Some(event_loop),
91 window_options: Some(builder.into()),
92 proxy,
93 cursor_visibility,
94 should_close: false,
95 close_key,
96 target_fps,
97 last_frame: Instant::now(),
98 current_frametime: Instant::now(),
99 spin_sleeper: SpinSleeper::default(),
100 size,
101 context: None,
102 resource_manager,
103 loader: Loader::new(),
104 defualt_resources,
105 ma_frame_time: 0.0,
106 })
107 }
108
109 pub fn is_key_down(&self, key: Key) -> bool {
111 self.input_handle.is_key_down(key)
112 }
113
114 pub fn is_key_up(&self, key: Key) -> bool {
116 self.input_handle.is_key_up(key)
117 }
118
119 pub fn is_key_pressed(&self, key: Key) -> bool {
121 self.input_handle.is_key_pressed(key)
122 }
123
124 pub fn is_key_released(&self, key: Key) -> bool {
126 self.input_handle.is_key_released(key)
127 }
128
129 pub fn check_modifiers(&self, modifer: ModifierKeys) -> bool {
132 self.input_handle.check_modifiers(modifer)
133 }
134
135 pub fn get_current_text(&self) -> Option<&str> {
138 self.input_handle.get_text_value()
139 }
140
141 pub fn is_mouse_key_down(&self, key: MouseKey) -> bool {
143 self.input_handle.is_mouse_key_down(key)
144 }
145
146 pub fn is_mouse_key_up(&self, key: MouseKey) -> bool {
148 self.input_handle.is_mouse_key_up(key)
149 }
150
151 pub fn is_mouse_key_pressed(&self, key: MouseKey) -> bool {
153 self.input_handle.is_mouse_key_pressed(key)
154 }
155
156 pub fn is_mouse_key_released(&self, key: MouseKey) -> bool {
158 self.input_handle.is_mouse_key_released(key)
159 }
160
161 pub fn get_mouse_position(&self) -> Vec2<f32> {
163 self.input_handle.get_mouse_position()
164 }
165
166 pub fn get_mouse_delta(&self) -> Vec2<f32> {
168 self.input_handle.get_mouse_delta()
169 }
170
171 pub fn window_has_focus(&self) -> bool {
175 let context = self
176 .context
177 .as_ref()
178 .expect("Context hasnt been created yet run inside impl Game");
179 context.window.has_focus()
180 }
181
182 pub fn is_window_maximized(&self) -> bool {
186 let context = self
187 .context
188 .as_ref()
189 .expect("Context hasnt been created yet run inside impl Game");
190 context.window.is_maximized()
191 }
192
193 pub fn is_window_minimized(&self) -> bool {
197 let context = self
198 .context
199 .as_ref()
200 .expect("Context hasnt been created yet run inside impl Game");
201 context.window.is_minimized().unwrap_or(false)
202 }
203
204 pub fn is_window_fullscreen(&self) -> bool {
208 let context = self
210 .context
211 .as_ref()
212 .expect("Context hasnt been created yet run inside impl Game");
213 context.window.fullscreen().is_some()
214 }
215
216 pub fn maximize_window(&self) {
220 let context = self
221 .context
222 .as_ref()
223 .expect("Context hasnt been created yet run inside impl Game");
224 context.window.set_maximized(true);
225 }
226
227 pub fn minimize_window(&self) {
231 let context = self
232 .context
233 .as_ref()
234 .expect("Context hasnt been created yet run inside impl Game");
235 context.window.set_minimized(true);
236 }
237
238 pub fn close(&mut self) {
240 self.should_close = true;
241 }
242
243 pub fn set_window_icon(&self, path: &str) -> Result<(), IconError> {
248 let image = image::open(path)?.into_rgba8();
249 let (width, height) = image.dimensions();
250 let image_bytes = image.into_raw();
251 let icon = winit::window::Icon::from_rgba(image_bytes, width, height)?;
252
253 let context = self
254 .context
255 .as_ref()
256 .expect("Context hasnt been created yet run inside impl Game");
257
258 context.window.set_window_icon(Some(icon));
259 Ok(())
260 }
261
262 pub fn set_window_title(&self, title: &str) {
266 let context = self
267 .context
268 .as_ref()
269 .expect("Context hasnt been created yet run inside impl Game");
270 context.window.set_title(title);
271 }
272
273 pub fn set_window_position(&self, x: f32, y: f32) {
277 let context = self
278 .context
279 .as_ref()
280 .expect("Context hasnt been created yet run inside impl Game");
281 context
282 .window
283 .set_outer_position(winit::dpi::PhysicalPosition::new(x, y));
284 }
285
286 pub fn set_window_min_size(&self, width: f32, height: f32) {
290 let context = self
291 .context
292 .as_ref()
293 .expect("Context hasnt been created yet run inside impl Game");
294 context
295 .window
296 .set_min_inner_size(Some(winit::dpi::PhysicalSize::new(width, height)));
297 }
298
299 pub fn get_window_position(&self) -> Option<Vec2<i32>> {
303 let context = self
304 .context
305 .as_ref()
306 .expect("Context hasnt been created yet run inside impl Game");
307 match context.window.outer_position() {
308 Ok(v) => Some((v.x, v.y).into()),
309 Err(_) => None,
310 }
311 }
312
313 pub fn get_window_size(&self) -> Vec2<u32> {
315 self.size
316 }
317
318 pub fn get_window_scale_factor(&self) -> f64 {
322 let context = self
323 .context
324 .as_ref()
325 .expect("Context hasnt been created yet run inside impl Game");
326 context.window.scale_factor()
327 }
328
329 pub fn toggle_fullscreen(&self) {
335 let context = self
336 .context
337 .as_ref()
338 .expect("Context hasnt been created yet run inside impl Game");
339 if self.is_window_fullscreen() {
340 context
341 .window
342 .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
343 } else {
344 context.window.set_fullscreen(None);
345 }
346 }
347
348 pub fn hide_cursor(&mut self) {
352 let context = self
353 .context
354 .as_ref()
355 .expect("Context hasnt been created yet run inside impl Game");
356 context.window.set_cursor_visible(false);
357 self.cursor_visibility = false;
358 }
359
360 pub fn show_cursor(&mut self) {
364 let context = self
365 .context
366 .as_ref()
367 .expect("Context hasnt been created yet run inside impl Game");
368 context.window.set_cursor_visible(true);
369 self.cursor_visibility = true;
370 }
371
372 pub fn get_frame_delta_time(&self) -> f32 {
374 Instant::now().duration_since(self.last_frame).as_secs_f32()
375 }
376
377 pub fn get_stable_fps(&self) -> f32 {
379 1.0 / self.ma_frame_time
380 }
381
382 pub fn get_target_fps(&self) -> Option<u16> {
384 self.target_fps
385 }
386
387 pub fn remove_target_fps(&mut self) {
391 self.target_fps = None;
392 }
393
394 pub fn remove_vsync(&mut self) {
400 let context = self
401 .context
402 .as_mut()
403 .expect("Context hasnt been created yet run inside impl Game");
404 context.config.present_mode = wgpu::PresentMode::AutoNoVsync;
405 context
406 .surface
407 .configure(&context.wgpu.device, &context.config);
408 }
409
410 pub fn add_vsync(&mut self) {
416 let context = self
417 .context
418 .as_mut()
419 .expect("Context hasnt been created yet run inside impl Game");
420 context.config.present_mode = wgpu::PresentMode::AutoVsync;
421 context
422 .surface
423 .configure(&context.wgpu.device, &context.config);
424 }
425
426 pub fn set_target_fps(&mut self, fps: u16) {
431 self.target_fps = Some(fps);
432 }
433
434 pub fn measure_string(&mut self, text: &str, font_size: f32, line_height: f32) -> Vec2<f32> {
439 let size = self.get_window_size();
440 let scale_factor = self.get_window_scale_factor();
441
442 let context = self
443 .context
444 .as_mut()
445 .expect("Context hasnt been created yet run inside impl Game");
446
447 let mut buffer = glyphon::Buffer::new(
448 &mut context.text_renderer.font_system,
449 Metrics::new(font_size, line_height),
450 );
451
452 let physical_width = (size.x as f64 * scale_factor) as f32;
453 let physical_height = (size.y as f64 * scale_factor) as f32;
454
455 buffer.set_size(
456 &mut context.text_renderer.font_system,
457 Some(physical_height),
458 Some(physical_width),
459 );
460 buffer.set_text(
461 &mut context.text_renderer.font_system,
462 text,
463 Attrs::new(),
464 Shaping::Basic,
465 );
466
467 let height = buffer.lines.len() as f32 * buffer.metrics().line_height;
468 let run_width = buffer
469 .layout_runs()
470 .map(|run| run.line_w)
471 .max_by(f32::total_cmp)
472 .unwrap_or(0.0);
473
474 Vec2 {
475 x: run_width,
476 y: height,
477 }
478 }
479
480 pub fn create_resource<P: AsRef<Path>>(
482 &mut self,
483 path: P,
484 loading_op: LoadingOp,
485 ) -> ResourceId<Vec<u8>> {
486 let typed_id = resource::generate_id::<Vec<u8>>();
487 let id = typed_id.get_id();
488 let path = path.as_ref();
489 let ip_resource = InProgressResource::new(path, id, ResourceType::Bytes, loading_op);
490
491 self.loader.blocking_load(ip_resource, self.proxy.clone());
492 typed_id
493 }
494
495 pub fn get_loading_resource_count(&self) -> usize {
498 self.loader.get_loading_resources()
499 }
500
501 pub fn get_byte_resource(&self, id: ResourceId<Vec<u8>>) -> Option<&Vec<u8>> {
505 self.resource_manager.get_byte_resource(&id)
506 }
507
508 pub(crate) fn get_proxy(&self) -> EventLoopProxy<BpEvent> {
509 self.proxy.clone()
510 }
511
512 pub(crate) fn get_resources(&self) -> &ResourceManager {
513 &self.resource_manager
514 }
515
516 pub(crate) fn defualt_material_bg_id(&self) -> ResourceId<Texture> {
517 self.defualt_resources.defualt_texture_id
518 }
519
520 pub(crate) fn defualt_pipe_id(&self) -> ResourceId<Shader> {
521 self.defualt_resources.default_pipeline_id
522 }
523
524 pub(crate) fn line_pipe_id(&self) -> ResourceId<Shader> {
525 self.defualt_resources.line_pipeline_id
526 }
527
528 pub fn run<T>(mut self, game: T)
530 where
531 T: Game + 'static,
532 {
533 let event_loop = self.event_loop.take().unwrap(); event_loop.run_app(&mut (self, game)).unwrap();
535 }
536
537 fn update(&mut self, elwt: &ActiveEventLoop) {
538 self.last_frame = Instant::now();
539 let dt = self
540 .last_frame
541 .duration_since(self.current_frametime)
542 .as_secs_f32();
543
544 self.ma_frame_time = (self.ma_frame_time + dt) / 2.0;
545
546 if self.should_close {
547 elwt.exit();
548 return;
549 }
550
551 self.input_handle.end_of_frame_refresh();
552 if let Some(key) = self.close_key {
553 if self.input_handle.is_key_down(key) {
554 elwt.exit();
555 return;
556 }
557 }
558
559 if let Some(frame_rate) = self.target_fps {
560 let frame_time = Instant::now()
561 .duration_since(self.current_frametime)
562 .as_nanos() as u64;
563 let desired_time_between_frames = 1000000000 / frame_rate as u64;
564 if frame_time < desired_time_between_frames {
565 self.spin_sleeper
566 .sleep_ns(desired_time_between_frames - frame_time);
567 }
568 }
569 }
570
571 fn input(&mut self, event: &WindowEvent) -> bool {
572 self.input_handle.process_input(event)
573 }
574
575 fn resize(&mut self, new_size: Vec2<u32>) {
576 let context = self
577 .context
578 .as_mut()
579 .expect("Context hasnt been created yet run inside impl Game");
580
581 log::info!("RESZING");
582
583 if new_size.x > 0 && new_size.y > 0 {
584 context.config.width = new_size.x;
585 context.config.height = new_size.y;
586 context
587 .surface
588 .configure(&context.wgpu.device, &context.config);
589 self.size = new_size;
590 context.wgpu.queue.write_buffer(
591 &context.camera_buffer,
592 48,
593 bytemuck::cast_slice(&[new_size.x as f32, new_size.y as f32]),
594 );
595 }
596 }
597
598 fn handle_resource(&mut self, resource: Result<Resource, ResourceError>) {
599 match resource {
600 Ok(data) => {
601 self.loader.remove_item_loading(data.loading_op);
602 match data.resource_type {
603 ResourceType::Bytes => self.add_finished_bytes(data.data, data.id, &data.path),
604 ResourceType::Image(mag, min) => {
605 self.add_finished_image(data.data, data.id, mag, min, &data.path)
606 }
607 ResourceType::Shader(options) => {
608 self.add_finished_shader(data.data, data.id, options, &data.path)
609 }
610 ResourceType::Font => self.add_finished_font(data),
611 }
612 }
613 Err(e) => {
614 log::error!(
615 "could not load resource: {:?}, becuase: {:?}, loading defualt replacement",
616 e,
617 e.error
618 );
619
620 self.loader.remove_item_loading(e.loading_op);
621
622 match e.resource_type {
623 ResourceType::Bytes => self.add_defualt_bytes(e.id),
624 ResourceType::Image(..) => self.add_defualt_image(e.id),
625 ResourceType::Shader(_) => self.add_defualt_shader(e.id),
626 ResourceType::Font => self.add_defualt_font(e.id),
627 }
628 }
629 }
630 }
631
632 fn add_finished_bytes(&mut self, data: Vec<u8>, id: NonZeroU64, path: &Path) {
633 let typed_id: ResourceId<Vec<u8>> = ResourceId::from_number(id);
634 self.resource_manager.insert_bytes(typed_id, data);
635 log::info!("byte resource at: {:?} loaded succesfully", path);
636 }
637
638 fn add_finished_image(
639 &mut self,
640 data: Vec<u8>,
641 id: NonZeroU64,
642 mag: SamplerType,
643 min: SamplerType,
644 path: &Path,
645 ) {
646 let typed_id: ResourceId<Texture> = ResourceId::from_number(id);
647 let texture = Texture::from_resource_data(self, None, data, mag, min);
648 match texture {
649 Ok(texture) => {
650 self.resource_manager.insert_texture(typed_id, texture);
651 log::info!("texture resource at: {:?} loaded succesfully", path);
652 }
653 Err(e) => log::error!("{:?}, loading defualt replacement", e),
654 }
655 }
656
657 fn add_finished_shader(
658 &mut self,
659 data: Vec<u8>,
660 id: NonZeroU64,
661 options: IntermediateOptions,
662 path: &Path,
663 ) {
664 let final_option =
665 FinalShaderOptions::from_intermediate(options, self.context.as_ref().unwrap());
666
667 let typed_id: ResourceId<Shader> = ResourceId::from_number(id);
668 let shader = Shader::from_resource_data(&data, final_option, self);
669 match shader {
670 Ok(shader) => {
671 self.resource_manager.insert_pipeline(typed_id, shader);
672 log::info!("shader resource at: {:?} loaded succesfully", path);
673 }
674 Err(e) => {
675 log::error!("{:?}. loading defualt replacement", e);
676 self.add_defualt_shader(id);
677 }
678 }
679 }
680
681 fn add_finished_font(&mut self, resource: Resource) {
682 let typed_id: ResourceId<Font> = ResourceId::from_number(resource.id);
683
684 let context = self.context.as_mut().unwrap();
685
686 let font = context.text_renderer.load_font_from_bytes(&resource.data);
687 self.resource_manager.insert_font(typed_id, font);
688 log::info!("Font resource at: {:?} loaded succesfully", resource.path);
689 }
690
691 fn add_defualt_bytes(&mut self, id: NonZeroU64) {
692 let typed_id: ResourceId<Vec<u8>> = ResourceId::from_number(id);
693 self.resource_manager.insert_bytes(typed_id, Vec::new());
694 }
695
696 fn add_defualt_image(&mut self, id: NonZeroU64) {
697 let typed_id: ResourceId<Texture> = ResourceId::from_number(id);
698 let image = Texture::default(self);
699 self.resource_manager.insert_texture(typed_id, image);
700 }
701
702 fn add_defualt_shader(&mut self, id: NonZeroU64) {
703 let context = self
704 .context
705 .as_ref()
706 .expect("Context hasnt been created yet run inside impl Game");
707
708 let typed_id: ResourceId<Shader> = ResourceId::from_number(id);
709 let shader = Shader::defualt(&context.wgpu, context.get_texture_format());
710 self.resource_manager.insert_pipeline(typed_id, shader);
711 }
712
713 fn add_defualt_font(&mut self, id: NonZeroU64) {
714 let context = self
715 .context
716 .as_ref()
717 .expect("Context hasnt been created yet run inside impl Game");
718
719 let typed_id: ResourceId<Font> = ResourceId::from_number(id);
720 let font = Font::from_str(context.text_renderer.get_defualt_font_name());
721 self.resource_manager.insert_font(typed_id, font);
722 }
723
724 pub(crate) fn is_loading(&self) -> bool {
725 #[cfg(not(target_arch = "wasm32"))]
727 {
728 false
729 }
730
731 #[cfg(target_arch = "wasm32")]
732 {
733 self.loader.is_blocked()
734 }
735 }
736}
737
738impl<T: Game> ApplicationHandler<BpEvent> for (Engine, T) {
739 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
740 let (engine, _) = self;
741
742 if engine.context.is_none() {
743 engine.context = Some(GraphicsContext::from_active_loop(
744 event_loop,
745 engine.window_options.take().unwrap(),
746 &mut engine.resource_manager,
747 &engine.defualt_resources,
748 ))
749 }
750 }
751
752 fn window_event(
753 &mut self,
754 event_loop: &ActiveEventLoop,
755 window_id: winit::window::WindowId,
756 event: WindowEvent,
757 ) {
758 let (engine, game) = self;
759 if window_id == engine.context.as_ref().unwrap().window.id() && !engine.input(&event) {
760 match event {
761 WindowEvent::CloseRequested => event_loop.exit(),
762 WindowEvent::Resized(physical_size) => {
763 engine.resize(physical_size.into());
764 game.on_resize(physical_size.into(), engine);
765 }
766 WindowEvent::RedrawRequested => {
767 if engine.is_loading() {
768 engine.update(event_loop);
769 } else {
770 game.update(engine);
771 engine.update(event_loop);
772 engine.current_frametime = Instant::now();
773
774 match render(game, engine) {
775 Ok(_) => {}
776 Err(wgpu::SurfaceError::Lost) => engine.resize(engine.size),
778 Err(wgpu::SurfaceError::OutOfMemory) => {
779 event_loop.exit();
780 }
781 Err(e) => eprintln!("{:?}", e),
782 }
783 }
784 }
785 e => {
786 log::info!("{:?}", e);
787 }
788 }
789 }
790 }
791
792 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: BpEvent) {
793 let (engine, _) = self;
794 match event {
795 BpEvent::ResourceLoaded(resource) => engine.handle_resource(resource),
796 }
797 }
798
799 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
800 let (engine, _) = self;
801 engine.context.as_ref().unwrap().window.request_redraw();
802 }
803}
804
805pub struct EngineBuilder {
808 pub(crate) resolution: (u32, u32),
809 pub(crate) full_screen: bool,
810 target_fps: Option<u16>,
811 close_key: Option<Key>,
812 pub(crate) window_icon: Option<winit::window::Icon>,
813 pub(crate) window_title: String,
814 pub(crate) resizable: bool,
815 pub(crate) vsync: wgpu::PresentMode,
816}
817
818impl EngineBuilder {
819 pub fn new() -> Self {
832 Self {
833 resolution: (600, 600),
834 full_screen: false,
835 target_fps: None,
836 close_key: None,
837 window_icon: None,
838 window_title: "Bottomless-Pit Game".into(),
839 resizable: true,
840 vsync: wgpu::PresentMode::AutoVsync,
841 }
842 }
843
844 pub fn with_resolution(self, resolution: (u32, u32)) -> Self {
846 Self {
847 resolution,
848 full_screen: self.full_screen,
849 target_fps: self.target_fps,
850 close_key: self.close_key,
851 window_icon: self.window_icon,
852 window_title: self.window_title,
853 resizable: self.resizable,
854 vsync: self.vsync,
855 }
856 }
857
858 pub fn fullscreen(self) -> Self {
860 Self {
861 resolution: self.resolution,
862 full_screen: true,
863 target_fps: self.target_fps,
864 close_key: self.close_key,
865 window_icon: self.window_icon,
866 window_title: self.window_title,
867 resizable: self.resizable,
868 vsync: self.vsync,
869 }
870 }
871
872 pub fn set_target_fps(self, fps: u16) -> Self {
877 Self {
878 resolution: self.resolution,
879 full_screen: self.full_screen,
880 target_fps: Some(fps),
881 close_key: self.close_key,
882 window_icon: self.window_icon,
883 window_title: self.window_title,
884 resizable: self.resizable,
885 vsync: self.vsync,
886 }
887 }
888
889 pub fn remove_vsync(self) -> Self {
894 Self {
895 resolution: self.resolution,
896 full_screen: self.full_screen,
897 target_fps: self.target_fps,
898 close_key: self.close_key,
899 window_icon: self.window_icon,
900 window_title: self.window_title,
901 resizable: self.resizable,
902 vsync: wgpu::PresentMode::AutoNoVsync,
903 }
904 }
905
906 pub fn set_close_key(self, key: Key) -> Self {
908 Self {
909 resolution: self.resolution,
910 full_screen: self.full_screen,
911 target_fps: self.target_fps,
912 close_key: Some(key),
913 window_icon: self.window_icon,
914 window_title: self.window_title,
915 resizable: self.resizable,
916 vsync: self.vsync,
917 }
918 }
919
920 pub fn set_window_title(self, title: &str) -> Self {
922 Self {
923 resolution: self.resolution,
924 full_screen: self.full_screen,
925 target_fps: self.target_fps,
926 close_key: self.close_key,
927 window_icon: self.window_icon,
928 window_title: title.into(),
929 resizable: self.resizable,
930 vsync: self.vsync,
931 }
932 }
933
934 pub fn set_window_icon(self, icon: winit::window::Icon) -> Self {
936 Self {
937 resolution: self.resolution,
938 full_screen: self.full_screen,
939 target_fps: self.target_fps,
940 close_key: self.close_key,
941 window_icon: Some(icon),
942 window_title: self.window_title,
943 resizable: self.resizable,
944 vsync: self.vsync,
945 }
946 }
947
948 pub fn unresizable(self) -> Self {
950 Self {
951 resolution: self.resolution,
952 full_screen: self.full_screen,
953 target_fps: self.target_fps,
954 close_key: self.close_key,
955 window_icon: self.window_icon,
956 window_title: self.window_title,
957 resizable: false,
958 vsync: self.vsync,
959 }
960 }
961
962 pub fn build(self) -> Result<Engine, BuildError> {
964 Engine::new(self)
965 }
966}
967
968impl Default for EngineBuilder {
969 fn default() -> Self {
970 Self::new()
971 }
972}
973
974#[derive(Debug)]
976pub enum BuildError {
977 WindowOsError(OsError),
980 CreateSurfaceError(CreateSurfaceError),
983 FailedToCreateAdapter,
985 RequestDeviceError(RequestDeviceError),
988 #[cfg(target_arch = "wasm32")]
989 CantGetWebWindow,
991 #[cfg(target_arch = "wasm32")]
992 CantGetDocument,
994 #[cfg(target_arch = "wasm32")]
995 CantGetBody,
998 #[cfg(target_arch = "wasm32")]
999 JsError(wasm_bindgen::JsValue),
1000}
1001
1002impl std::fmt::Display for BuildError {
1003 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1004 match self {
1005 Self::WindowOsError(e) => write!(f, "{}", e),
1006 Self::CreateSurfaceError(e) => write!(f, "{}", e),
1007 Self::FailedToCreateAdapter => write!(f, "unable to create Adapater"),
1008 Self::RequestDeviceError(e) => write!(f, "{}", e),
1009 #[cfg(target_arch = "wasm32")]
1010 Self::CantGetWebWindow => write!(f, "could not get web Window"),
1011 #[cfg(target_arch = "wasm32")]
1012 Self::CantGetDocument => write!(f, "could not get HTML document"),
1013 #[cfg(target_arch = "wasm32")]
1014 Self::CantGetBody => write!(f, "could nto get HTML body tag"),
1015 #[cfg(target_arch = "wasm32")]
1016 Self::JsError(e) => write!(f, "{:?}", e),
1017 }
1018 }
1019}
1020
1021impl std::error::Error for BuildError {}
1022
1023impl From<OsError> for BuildError {
1024 fn from(value: OsError) -> Self {
1025 Self::WindowOsError(value)
1026 }
1027}
1028
1029impl From<CreateSurfaceError> for BuildError {
1030 fn from(value: CreateSurfaceError) -> Self {
1031 Self::CreateSurfaceError(value)
1032 }
1033}
1034
1035impl From<RequestDeviceError> for BuildError {
1036 fn from(value: RequestDeviceError) -> Self {
1037 Self::RequestDeviceError(value)
1038 }
1039}
1040
1041#[cfg(target_arch = "wasm32")]
1042impl From<wasm_bindgen::JsValue> for BuildError {
1043 fn from(value: wasm_bindgen::JsValue) -> Self {
1044 Self::JsError(value)
1045 }
1046}
1047
1048#[derive(Debug)]
1050pub enum IconError {
1051 BadIcon(BadIcon),
1054 IconLoadingError(ImageError),
1056}
1057
1058impl From<BadIcon> for IconError {
1059 fn from(value: BadIcon) -> Self {
1060 Self::BadIcon(value)
1061 }
1062}
1063
1064impl From<ImageError> for IconError {
1065 fn from(value: ImageError) -> Self {
1066 Self::IconLoadingError(value)
1067 }
1068}
1069
1070impl std::fmt::Display for IconError {
1071 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072 match self {
1073 Self::BadIcon(e) => write!(f, "{}", e),
1074 Self::IconLoadingError(e) => write!(f, "{}", e),
1075 }
1076 }
1077}
1078
1079impl std::error::Error for IconError {}
1080
1081pub(crate) struct DefualtResources {
1082 pub(crate) defualt_texture_id: ResourceId<Texture>,
1083 pub(crate) default_pipeline_id: ResourceId<Shader>,
1084 pub(crate) line_pipeline_id: ResourceId<Shader>,
1085}
1086
1087#[derive(Debug)]
1088pub(crate) enum BpEvent {
1089 ResourceLoaded(Result<Resource, ResourceError>),
1090}