1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::sync::Arc;

use game_loop::{GameLoop, Time};
use miette::{Context, IntoDiagnostic, Result};
use pixels::{
    wgpu::{BlendState, Color},
    Pixels, PixelsBuilder, SurfaceTexture,
};

use vek::Vec2;
use winit::{
    event::Event,
    event_loop::EventLoop,
    window::{Window, WindowBuilder},
};
use winit_input_helper::WinitInputHelper;

use crate::canvas::Canvas;

use super::WindowConfig;

/// Desktop implementation of opening a window.
pub(crate) fn window<G, U, R, H>(
    window_builder: WindowBuilder,
    game_state: G,
    WindowConfig {
        buffer_size,
        scaling,
        title: _,
        updates_per_second,
    }: WindowConfig,
    mut update: U,
    mut render: R,
    event: H,
) -> Result<()>
where
    G: 'static,
    U: FnMut(&mut G, &WinitInputHelper, Option<Vec2<usize>>, f32) -> bool + 'static,
    R: FnMut(&mut G, &mut Canvas, f32) + 'static,
    H: FnMut(&mut GameLoop<(G, Pixels, WinitInputHelper), Time, Arc<Window>>, &Event<'_, ()>)
        + 'static,
{
    let event_loop = EventLoop::new();
    let window = window_builder
        .build(&event_loop)
        .into_diagnostic()
        .wrap_err("Error setting up window")?;

    // Setup the pixel surface
    let surface_texture = SurfaceTexture::new(
        (buffer_size.w * scaling) as u32,
        (buffer_size.h * scaling) as u32,
        &window,
    );
    let pixels = PixelsBuilder::new(buffer_size.w as u32, buffer_size.h as u32, surface_texture)
        .clear_color(Color::WHITE)
        .blend_state(BlendState::REPLACE)
        .build()
        .into_diagnostic()
        .wrap_err("Error setting up pixels buffer")?;

    // Open the window and run the event loop
    let mut buffer = vec![0u32; buffer_size.w * buffer_size.h];

    // Handle input
    let input = WinitInputHelper::new();

    game_loop::game_loop(
        event_loop,
        Arc::new(window),
        (game_state, pixels, input),
        updates_per_second,
        0.1,
        move |g| {
            // Calculate mouse in pixels
            let mouse = g.game.2.mouse().and_then(|mouse| {
                g.game
                    .1
                    .window_pos_to_pixel(mouse)
                    .map(|(x, y)| Vec2::new(x, y))
                    .ok()
            });

            // Call update and exit when it returns true
            if update(
                &mut g.game.0,
                &g.game.2,
                mouse,
                (updates_per_second as f32).recip(),
            ) {
                g.exit();
            }
        },
        move |g| {
            let frame_time = g.last_frame_time();

            // Wrap the buffer in a canvas with the size
            let buffer = buffer.as_mut_slice();
            let size = buffer_size;
            let mut canvas = Canvas { size, buffer };

            render(&mut g.game.0, &mut canvas, frame_time as f32);

            // Blit draws the pixels in RGBA format, but the pixels crate expects BGRA, so convert it
            g.game
                .1
                .frame_mut()
                .chunks_exact_mut(4)
                .zip(buffer.iter())
                .for_each(|(target, source)| {
                    let source = source.to_ne_bytes();
                    target[0] = source[2];
                    target[1] = source[1];
                    target[2] = source[0];
                    target[3] = source[3];
                });

            // Render the pixel buffer
            if let Err(err) = g.game.1.render() {
                dbg!(err);
                // TODO: properly handle error
                g.exit();
            }
        },
        event,
    );
}