1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2#![warn(missing_docs)]
3
4pub use miniquad;
5pub use rgb;
6pub use simple_blit;
7
8use miniquad::{
9 conf::Conf, window, Backend, Bindings, BufferLayout, BufferSource, BufferType, BufferUsage,
10 CursorIcon, EventHandler, FilterMode, KeyCode, KeyMods, MipmapFilterMode, MouseButton,
11 PassAction, Pipeline, PipelineParams, RenderingBackend, ShaderMeta, ShaderSource,
12 TextureFormat, TextureId, TextureKind, TextureParams, TextureWrap, UniformBlockLayout,
13 VertexAttribute, VertexFormat,
14};
15use rgb::{ComponentBytes, RGBA8};
16use rustc_hash::FxHashMap;
17use simple_blit::{GenericSurface, Surface};
18use std::{
19 future,
20 sync::{mpsc, Arc, Mutex},
21 task::Poll,
22 time::Duration,
23};
24
25#[repr(C)]
26struct Vec2 {
27 x: f32,
28 y: f32,
29}
30
31impl Vec2 {
32 #[inline]
33 pub const fn new(x: f32, y: f32) -> Self {
34 Self { x, y }
35 }
36}
37
38#[repr(C)]
39struct Vertex {
40 pos: Vec2,
41 uv: Vec2,
42}
43
44const SHADER_VERT: &str = r#"#version 100
45attribute vec2 pos;
46attribute vec2 uv;
47
48varying lowp vec2 texcoord;
49
50void main() {
51 gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
52 texcoord = uv;
53}"#;
54
55const SHADER_FRAG: &str = r#"#version 100
56varying lowp vec2 texcoord;
57
58uniform sampler2D tex;
59
60void main() {
61 gl_FragColor = texture2D(tex, texcoord);
62}"#;
63
64const SHADER_METAL: &str = r#"
65#include <metal_stdlib>
66
67using namespace metal;
68
69struct Vertex {
70 float2 pos [[attribute(0)]];
71 float2 uv [[attribute(1)]];
72};
73
74struct FragData {
75 float4 position [[position]];
76 float2 uv [[user(locn0)]];
77};
78
79vertex FragData vertexShader(
80 Vertex v [[stage_in]]
81) {
82 FragData out;
83
84 out.position = float4(v.pos.x, v.pos.y, 0.0, 1.0);
85 out.uv = v.uv;
86
87 return out;
88}
89
90fragment float4 fragmentShader(
91 FragData in [[stage_in]],
92 texture2d<float> tex [[texture(0)]],
93 sampler texSmplr [[sampler(0)]]
94) {
95 return tex.sample(texSmplr, in.uv);
96}
97"#;
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum InputState {
102 Pressed,
104 Down,
106 Released,
108}
109
110pub struct Context {
112 backend: Box<dyn RenderingBackend>,
113
114 pipeline: Pipeline,
115 bindings: Bindings,
116
117 instant: f64,
118 delta_time: f64,
119
120 clear_color: RGBA8,
121 framebuffer: Vec<RGBA8>,
122 buf_width: u32,
123 buf_height: u32,
124
125 keys: FxHashMap<KeyCode, InputState>,
126 key_mods: KeyMods,
127 mouse_pos: (f32, f32),
128 mouse_wheel: (f32, f32),
129 mouse_buttons: FxHashMap<MouseButton, InputState>,
130}
131
132impl Context {
133 #[inline]
134 fn texture_params(width: u32, height: u32) -> TextureParams {
135 TextureParams {
136 kind: TextureKind::Texture2D,
137 format: TextureFormat::RGBA8,
138 wrap: TextureWrap::Clamp,
139 min_filter: FilterMode::Nearest,
140 mag_filter: FilterMode::Nearest,
141 mipmap_filter: MipmapFilterMode::None,
142 width,
143 height,
144 ..Default::default()
145 }
146 }
147
148 fn new() -> Self {
149 let mut backend = window::new_rendering_backend();
150
151 let (win_width, win_height) = window::screen_size();
152 let (win_width, win_height) = (win_width as u32, win_height as u32);
153
154 #[rustfmt::skip]
155 let verices: [Vertex; 4] = [
156 Vertex { pos: Vec2::new(-1., -1.), uv: Vec2::new(0., 1.) },
157 Vertex { pos: Vec2::new( 1., -1.), uv: Vec2::new(1., 1.) },
158 Vertex { pos: Vec2::new( 1., 1.), uv: Vec2::new(1., 0.) },
159 Vertex { pos: Vec2::new(-1., 1.), uv: Vec2::new(0., 0.) },
160 ];
161 let vertex_buffer = backend.new_buffer(
162 BufferType::VertexBuffer,
163 BufferUsage::Immutable,
164 BufferSource::slice(&verices),
165 );
166
167 let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
168 let index_buffer = backend.new_buffer(
169 BufferType::IndexBuffer,
170 BufferUsage::Immutable,
171 BufferSource::slice(&indices),
172 );
173
174 let texture = backend.new_render_texture(Self::texture_params(win_width, win_height));
175
176 let bindings = Bindings {
177 vertex_buffers: vec![vertex_buffer],
178 index_buffer,
179 images: vec![texture],
180 };
181
182 let shader_meta = ShaderMeta {
183 images: vec!["tex".to_string()],
184 uniforms: UniformBlockLayout { uniforms: vec![] },
185 };
186
187 let shader = backend
188 .new_shader(
189 match backend.info().backend {
190 Backend::OpenGl => ShaderSource::Glsl {
191 vertex: SHADER_VERT,
192 fragment: SHADER_FRAG,
193 },
194 Backend::Metal => ShaderSource::Msl {
195 program: SHADER_METAL,
196 },
197 },
198 shader_meta,
199 )
200 .unwrap_or_else(|err| panic!("{err}"));
201
202 let pipeline = backend.new_pipeline(
203 &[BufferLayout::default()],
204 &[
205 VertexAttribute::new("pos", VertexFormat::Float2),
206 VertexAttribute::new("uv", VertexFormat::Float2),
207 ],
208 shader,
209 PipelineParams::default(),
210 );
211
212 Self {
213 backend,
214
215 pipeline,
216 bindings,
217
218 instant: miniquad::date::now(),
219 delta_time: 0.,
220
221 clear_color: RGBA8::new(0, 0, 0, 255),
222 framebuffer: vec![RGBA8::new(0, 0, 0, 255); (win_width * win_height) as usize],
223 buf_width: win_width,
224 buf_height: win_height,
225
226 keys: FxHashMap::default(),
227 key_mods: KeyMods {
228 shift: false,
229 ctrl: false,
230 alt: false,
231 logo: false,
232 },
233 mouse_pos: (0., 0.),
234 mouse_wheel: (0., 0.),
235 mouse_buttons: FxHashMap::default(),
236 }
237 }
238
239 #[inline]
240 fn texture(&self) -> TextureId {
241 self.bindings.images[0]
242 }
243
244 #[inline]
245 fn set_texture(&mut self, tex: TextureId) {
246 self.bindings.images[0] = tex;
247 }
248
249 pub fn load_file<F>(&self, path: impl AsRef<str>, on_loaded: F)
253 where
254 F: Fn(Result<Vec<u8>, miniquad::fs::Error>) + 'static,
255 {
256 miniquad::fs::load_file(path.as_ref(), on_loaded);
257 }
258
259 pub async fn load_file_async(
263 &self,
264 path: impl AsRef<str>,
265 ) -> Result<Vec<u8>, miniquad::fs::Error> {
266 let contents = Arc::new(Mutex::new(None));
267
268 {
269 let contents = contents.clone();
270
271 miniquad::fs::load_file(path.as_ref(), move |result| {
272 *contents.lock().unwrap() = Some(result);
273 });
274 }
275
276 future::poll_fn(move |_ctx| {
277 let mut result = contents.lock().unwrap();
278
279 if let Some(result) = result.take() {
280 Poll::Ready(result)
281 } else {
282 Poll::Pending
283 }
284 })
285 .await
286 }
287
288 #[inline]
293 pub fn load_file_channel(
294 &self,
295 path: impl AsRef<str>,
296 ) -> mpsc::Receiver<Result<Vec<u8>, miniquad::fs::Error>> {
297 let (sender, receiver) = mpsc::sync_channel(1);
298
299 miniquad::fs::load_file(path.as_ref(), move |result| {
300 let _ = sender.try_send(result);
301 });
302
303 receiver
304 }
305
306 #[inline]
310 pub fn display_width(&self) -> f32 {
311 window::screen_size().0
312 }
313
314 #[inline]
318 pub fn display_height(&self) -> f32 {
319 window::screen_size().1
320 }
321
322 #[inline]
324 pub fn buffer_width(&self) -> u32 {
325 self.buf_width
326 }
327
328 #[inline]
330 pub fn buffer_height(&self) -> u32 {
331 self.buf_height
332 }
333
334 #[inline]
339 pub fn dpi_scale(&self) -> f32 {
340 window::dpi_scale()
341 }
342
343 #[inline]
345 pub fn delta_time_secs(&self) -> f64 {
346 self.delta_time
347 }
348
349 #[inline]
351 pub fn delta_time(&self) -> Duration {
352 Duration::from_secs_f64(self.delta_time)
353 }
354
355 #[inline]
359 pub fn clear_color(&mut self, color: RGBA8) {
360 self.clear_color = color;
361 }
362
363 #[inline]
367 pub fn get_key_state(&self, key: KeyCode) -> Option<InputState> {
368 self.keys.get(&key).copied()
369 }
370
371 #[inline]
373 pub fn get_all_keys(&self) -> &FxHashMap<KeyCode, InputState> {
374 &self.keys
375 }
376
377 #[inline]
379 pub fn is_key_down(&self, key: KeyCode) -> bool {
380 self.get_key_state(key)
381 .map_or(false, |state| state != InputState::Released)
382 }
383
384 #[inline]
386 pub fn is_key_pressed(&self, key: KeyCode) -> bool {
387 self.get_key_state(key)
388 .map_or(false, |state| state == InputState::Pressed)
389 }
390
391 #[inline]
393 pub fn is_key_released(&self, key: KeyCode) -> bool {
394 self.get_key_state(key)
395 .map_or(false, |state| state == InputState::Released)
396 }
397
398 #[inline]
400 pub fn get_key_mods(&self) -> KeyMods {
401 self.key_mods
402 }
403
404 #[inline]
406 pub fn get_screen_mouse_pos(&self) -> (f32, f32) {
407 self.mouse_pos
408 }
409
410 #[inline]
412 pub fn get_framebuffer_mouse_pos(&self) -> (i32, i32) {
413 let (x, y) = self.mouse_pos;
414 let (win_width, win_height) = window::screen_size();
415
416 (
417 (x / win_width * self.buf_width as f32) as _,
418 (y / win_height * self.buf_height as f32) as _,
419 )
420 }
421
422 #[inline]
424 pub fn get_mouse_wheel(&self) -> (f32, f32) {
425 self.mouse_wheel
426 }
427
428 #[inline]
432 pub fn get_mouse_button_state(&self, button: MouseButton) -> Option<InputState> {
433 self.mouse_buttons.get(&button).copied()
434 }
435
436 #[inline]
438 pub fn get_all_mouse_buttons(&self) -> &FxHashMap<MouseButton, InputState> {
439 &self.mouse_buttons
440 }
441
442 #[inline]
444 pub fn is_mouse_button_down(&self, button: MouseButton) -> bool {
445 self.get_mouse_button_state(button)
446 .map_or(false, |state| state != InputState::Released)
447 }
448
449 #[inline]
451 pub fn is_mouse_button_pressed(&self, button: MouseButton) -> bool {
452 self.get_mouse_button_state(button)
453 .map_or(false, |state| state == InputState::Pressed)
454 }
455
456 #[inline]
458 pub fn is_mouse_button_released(&self, button: MouseButton) -> bool {
459 self.get_mouse_button_state(button)
460 .map_or(false, |state| state == InputState::Released)
461 }
462
463 #[inline]
465 pub fn quit(&self) {
466 window::request_quit();
467 }
468
469 #[inline]
471 pub fn show_mouse(&self, shown: bool) {
472 window::show_mouse(shown);
473 }
474
475 #[inline]
477 pub fn show_keyboard(&self, shown: bool) {
478 window::show_keyboard(shown);
479 }
480
481 #[inline]
483 pub fn set_mouse_cursor(&self, cursor_icon: CursorIcon) {
484 window::set_mouse_cursor(cursor_icon);
485 }
486
487 #[inline]
489 pub fn set_fullscreen(&self, fullscreen: bool) {
490 window::set_fullscreen(fullscreen);
491 }
492
493 #[inline]
495 pub fn get_clipboard(&self) -> Option<String> {
496 window::clipboard_get()
497 }
498
499 #[inline]
501 pub fn set_clipboard(&self, data: impl AsRef<str>) {
502 window::clipboard_set(data.as_ref());
503 }
504
505 #[inline]
511 pub fn set_window_size(&mut self, new_width: u32, new_height: u32) {
512 window::set_window_size(new_width, new_height);
513 }
514
515 pub fn set_framebuffer_size(&mut self, new_width: u32, new_height: u32) {
520 self.backend.delete_texture(self.texture());
523
524 let new_texture = self
525 .backend
526 .new_render_texture(Self::texture_params(new_width, new_height));
527 self.set_texture(new_texture);
528
529 self.buf_width = new_width;
530 self.buf_height = new_height;
531
532 self.framebuffer.fill(self.clear_color);
533 self.framebuffer
534 .resize((new_width * new_height) as usize, self.clear_color);
535 }
536
537 #[inline]
539 pub fn clear(&mut self) {
540 for pix in self.framebuffer.iter_mut() {
541 *pix = self.clear_color;
542 }
543 }
544
545 #[inline]
549 pub fn draw_pixel(&mut self, x: i32, y: i32, color: RGBA8) {
550 if let Some(pix) = self
551 .framebuffer
552 .get_mut(y as usize * self.buf_width as usize + x as usize)
553 {
554 *pix = color;
555 }
556 }
557
558 pub fn draw_rect(&mut self, x: i32, y: i32, width: u32, height: u32, color: RGBA8) {
562 simple_blit::blit(
563 self.as_mut_surface()
564 .offset_surface_mut([x as u32, y as _].into()),
565 simple_blit::SingleValueSurface::new(color, [width, height].into()),
566 &[],
567 );
568 }
569
570 pub fn draw_pixels(&mut self, x: i32, y: i32, width: u32, height: u32, pixels: &[RGBA8]) {
574 if let Some(buffer) = simple_blit::GenericSurface::new(pixels, [width, height].into()) {
575 simple_blit::blit(
576 self.as_mut_surface()
577 .offset_surface_mut([x as u32, y as _].into()),
578 buffer.sub_surface([0, 0].into(), [width, height].into()),
579 &[],
580 );
581 }
582 }
583
584 pub fn draw_screen(&mut self, pixels: &[RGBA8]) {
588 if let Some(buffer) = simple_blit::GenericSurface::new(
589 pixels,
590 simple_blit::size(self.buf_width, self.buf_height),
591 ) {
592 simple_blit::blit(self.as_mut_surface(), buffer, &[]);
593 }
594 }
595
596 #[inline]
598 pub fn get_draw_buffer(&self) -> &[RGBA8] {
599 &self.framebuffer
600 }
601
602 #[inline]
606 pub fn get_mut_draw_buffer(&mut self) -> &mut [RGBA8] {
607 &mut self.framebuffer
608 }
609
610 #[inline]
612 pub fn as_surface(&self) -> GenericSurface<&[RGBA8], RGBA8> {
613 GenericSurface::new(
614 &self.framebuffer[..],
615 simple_blit::size(self.buf_width, self.buf_height),
616 )
617 .unwrap()
618 }
619
620 #[inline]
622 pub fn as_mut_surface(&mut self) -> GenericSurface<&mut [RGBA8], RGBA8> {
623 GenericSurface::new(
624 &mut self.framebuffer[..],
625 simple_blit::size(self.buf_width, self.buf_height),
626 )
627 .unwrap()
628 }
629
630 #[inline]
632 pub fn set_texture_filter(&mut self, filter: FilterMode) {
633 self.backend
634 .texture_set_filter(self.texture(), filter, MipmapFilterMode::None);
635 }
636
637 #[inline]
639 pub fn get_rendering_backend(&self) -> &dyn RenderingBackend {
640 &*self.backend
641 }
642
643 #[inline]
645 pub fn get_mut_rendering_backend(&mut self) -> &mut dyn RenderingBackend {
646 &mut *self.backend
647 }
648}
649
650pub trait App {
652 fn update(&mut self, ctx: &mut Context);
654
655 fn draw(&mut self, ctx: &mut Context);
658}
659
660struct Handler<S: App> {
661 ctx: Context,
662 state: S,
663}
664
665impl<S> EventHandler for Handler<S>
666where
667 S: App,
668{
669 fn update(&mut self) {
670 let new_instant = miniquad::date::now();
671 self.ctx.delta_time = new_instant - self.ctx.instant;
672 self.ctx.instant = new_instant;
673
674 self.state.update(&mut self.ctx);
675
676 self.ctx.mouse_wheel = (0., 0.);
677
678 self.ctx.keys.retain(|_, state| match state {
679 InputState::Down => true,
680 InputState::Pressed => {
681 *state = InputState::Down;
682 true
683 }
684 InputState::Released => false,
685 });
686
687 self.ctx.mouse_buttons.retain(|_, state| match state {
688 InputState::Down => true,
689 InputState::Pressed => {
690 *state = InputState::Down;
691 true
692 }
693 InputState::Released => false,
694 });
695 }
696
697 fn draw(&mut self) {
698 self.state.draw(&mut self.ctx);
699
700 self.ctx
701 .backend
702 .texture_update(self.ctx.texture(), self.ctx.framebuffer.as_bytes());
703
704 self.ctx.backend.begin_default_pass(PassAction::Nothing);
705
706 self.ctx.backend.apply_pipeline(&self.ctx.pipeline);
707 self.ctx.backend.apply_bindings(&self.ctx.bindings);
708
709 self.ctx.backend.draw(0, 6, 1);
710
711 self.ctx.backend.end_render_pass();
712
713 self.ctx.backend.commit_frame();
714 }
715
716 #[inline]
717 fn key_down_event(&mut self, key_code: KeyCode, key_mods: KeyMods, repeat: bool) {
718 if !repeat {
719 self.ctx.keys.insert(key_code, InputState::Pressed);
720 }
721
722 self.ctx.key_mods = key_mods;
723 }
724
725 #[inline]
726 fn key_up_event(&mut self, key_code: KeyCode, key_mods: KeyMods) {
727 self.ctx.keys.insert(key_code, InputState::Released);
728 self.ctx.key_mods = key_mods;
729 }
730
731 #[inline]
732 fn mouse_button_down_event(&mut self, button: MouseButton, _x: f32, _y: f32) {
733 self.ctx.mouse_buttons.insert(button, InputState::Pressed);
734 }
735
736 #[inline]
737 fn mouse_button_up_event(&mut self, button: MouseButton, _x: f32, _y: f32) {
738 self.ctx.mouse_buttons.insert(button, InputState::Pressed);
739 }
740
741 #[inline]
742 fn mouse_motion_event(&mut self, x: f32, y: f32) {
743 self.ctx.mouse_pos = (x, y);
744 }
745
746 #[inline]
747 fn mouse_wheel_event(&mut self, x: f32, y: f32) {
748 self.ctx.mouse_wheel = (x, y);
749 }
750
751 #[inline]
752 fn char_event(&mut self, _character: char, key_mods: KeyMods, _repeat: bool) {
753 self.ctx.key_mods = key_mods;
754 }
755}
756
757#[inline]
759pub fn start(config: Conf, state: impl App + 'static) {
760 miniquad::start(config, move || {
761 Box::new(Handler {
762 ctx: Context::new(),
763 state,
764 })
765 })
766}