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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
//! Environment methods for the [`Engine`].
//!
//! Methods for reading and setting various engine environment values.
//!
//! Provided [`PixState`] methods:
//!
//! - [`PixState::focused`]: Whether the current window target has focus.
//! - [`PixState::delta_time`]: [Duration] elapsed since last frame.
//! - [`PixState::elapsed`]: [Duration] elapsed since application start.
//! - [`PixState::frame_count`]: Total number of frames since application start.
//! - [`PixState::redraw`]: Run render loop 1 time, calling [`PixEngine::on_update`].
//! - [`PixState::run_times`]: Run render loop N times, calling [`PixEngine::on_update`].
//! - [`PixState::avg_frame_rate`]: Average frames per second rendered.
//! - [`PixState::quit`]: Trigger application quit.
//! - [`PixState::abort_quit`]: Abort application quit.
//! - [`PixState::day`]: Return the current day between 1-31.
//! - [`PixState::month`]: Return the current month between 1-12.
//! - [`PixState::year`]: Return the current year as an integer.
//! - [`PixState::hour`]: Return the current hour between 0-23.
//! - [`PixState::minute`]: Return the current minute between 0-59.
//! - [`PixState::second`]: Return the current second between 0-59.

use crate::{
    prelude::*,
    renderer::{Rendering, WindowRenderer},
};
use std::time::{Duration, Instant};
use time::OffsetDateTime;

const ONE_SECOND: Duration = Duration::from_secs(1);

/// Environment values for [`PixState`]
#[derive(Debug, Clone)]
pub(crate) struct Environment {
    focused_window: Option<WindowId>,
    delta_time: Duration,
    start: Instant,
    frame_rate: f32,
    frame_count: usize,
    run_count: usize,
    quit: bool,
    last_frame_time: Instant,
    frame_timer: Duration,
}

impl Default for Environment {
    fn default() -> Self {
        Self {
            focused_window: None,
            delta_time: Duration::default(),
            start: Instant::now(),
            frame_rate: 0.0,
            frame_count: 0,
            run_count: 0,
            quit: false,
            last_frame_time: Instant::now(),
            frame_timer: Duration::default(),
        }
    }
}

impl PixState {
    /// Present all renderer changes since last frame.
    #[inline]
    pub fn present(&mut self) {
        self.renderer.present();
    }

    /// Returns whether the current window target has focus. To check focus of a specific window, see
    /// [`PixState::focused_window`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     if s.focused() {
    ///         // Update screen only when focused
    ///         s.rect([0, 0, 100, 100])?;
    ///     }
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub fn focused(&self) -> bool {
        self.env
            .focused_window
            .map(|focused_window| focused_window == self.renderer.window_id())
            .unwrap_or_default()
    }

    /// Returns whether a given window has focus. To check focus of a any window, see
    /// [`PixState::focused`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     if s.focused_window(s.window_id()) {
    ///         // Update screen only when focused
    ///         s.rect([0, 0, 100, 100])?;
    ///     }
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub fn focused_window(&self, window_id: WindowId) -> bool {
        matches!(self.env.focused_window, Some(id) if id == window_id)
    }

    /// The [Duration] elapsed since last frame.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { position: f64, velocity: f64 };
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     // Update position based on frame timestep
    ///     self.position = self.velocity * s.delta_time().as_secs_f64();
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub const fn delta_time(&self) -> Duration {
        self.env.delta_time
    }

    /// The [Duration[ elapsed since application start.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     // Draw a blinking box, indepdendent of frame rate
    ///     if s.elapsed().as_millis() >> 9 & 1 > 0 {
    ///         s.rect([0, 0, 10, 10])?;
    ///     }
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub fn elapsed(&self) -> Duration {
        self.env.start.elapsed()
    }

    /// The total number of frames rendered since application start.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     // Create a strobe effect, dependent on frame rate
    ///     if s.frame_count() % 5 == 0 {
    ///         s.rect([0, 0, 10, 10])?;
    ///     }
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub const fn frame_count(&self) -> usize {
        self.env.frame_count
    }

    /// Run the render loop 1 time by calling [`PixEngine::on_update`].
    ///
    /// This can be used to only redraw in response to user actions such as
    /// [`PixEngine::on_mouse_pressed`] or [`PixEngine::on_key_pressed`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.run(false); // Disable render loop
    ///     Ok(())
    /// }
    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
    ///     // Step one frame draw at a time on each space press
    ///     // Useful for debugging frame-by-frame
    ///     if let Key::Space = event.key {
    ///         s.redraw();
    ///         return Ok(true);
    ///     }
    ///     Ok(false)
    /// # }
    /// # }
    /// ```
    #[inline]
    pub fn redraw(&mut self) {
        self.env.run_count = 1;
    }

    /// Run the render loop N times by calling [`PixEngine::on_update`].
    ///
    /// This can be used to only redraw in response to user actions such as
    /// [`PixEngine::on_mouse_pressed`] or [`PixEngine::on_key_pressed`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.run(false); // Disable render loop
    ///     Ok(())
    /// }
    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
    ///     // Step one frame draw at a time on each space press
    ///     // Useful for debugging by multiple frames at a time
    ///     if let Key::Space = event.key {
    ///         s.run_times(4);
    ///         return Ok(true);
    ///     }
    ///     Ok(false)
    /// # }
    /// # }
    /// ```
    #[inline]
    pub fn run_times(&mut self, n: usize) {
        self.env.run_count = n;
    }

    /// The average frames per second rendered.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.text(format!("FPS: {}", s.avg_frame_rate()))?;
    ///     Ok(())
    /// # }
    /// # }
    /// ```
    #[inline]
    #[must_use]
    pub const fn avg_frame_rate(&self) -> f32 {
        self.env.frame_rate
    }

    /// Trigger application quit.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     if s.button("Quit?")? {
    ///         s.quit();
    ///     }
    ///     Ok(())
    /// # }
    /// # }
    /// ```
    #[inline]
    pub fn quit(&mut self) {
        self.env.quit = true;
    }

    /// Abort application quit and resume render loop by calling [`PixEngine::on_update`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { has_unsaved_changes: bool, prompt_save_dialog: bool }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     if self.has_unsaved_changes {
    ///         self.prompt_save_dialog = true;
    ///         s.abort_quit();
    ///     }
    ///     Ok(())
    /// # }
    /// # }
    /// ```
    #[inline]
    pub fn abort_quit(&mut self) {
        self.env.quit = false;
    }

    /// Return the current day between 1-31.
    #[inline]
    #[must_use]
    pub fn day() -> u32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .day() as u32
    }

    /// Return the current month between 1-12.
    #[inline]
    #[must_use]
    pub fn month() -> u32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .month() as u32
    }

    /// Return the current year as an integer.
    #[inline]
    #[must_use]
    pub fn year() -> i32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .year()
    }

    /// Return the current hour between 0-23.
    #[inline]
    #[must_use]
    pub fn hour() -> u32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .hour() as u32
    }

    /// Return the current minute between 0-59.
    #[inline]
    #[must_use]
    pub fn minute() -> u32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .minute() as u32
    }

    /// Return the current second between 0-59.
    #[inline]
    #[must_use]
    pub fn second() -> u32 {
        OffsetDateTime::now_local()
            .unwrap_or(OffsetDateTime::now_utc())
            .second() as u32
    }
}

impl PixState {
    /// Return the instant the last frame was rendered at.
    #[inline]
    pub(crate) const fn last_frame_time(&self) -> Instant {
        self.env.last_frame_time
    }

    /// Set the delta time since last frame.
    #[inline]
    pub(crate) fn set_delta_time(&mut self, now: Instant, time_since_last: Duration) {
        self.env.delta_time = time_since_last;
        self.env.last_frame_time = now;
    }

    /// Whether the current render loop should be running or not.
    #[inline]
    pub(crate) const fn is_running(&self) -> bool {
        self.settings.running || self.env.run_count > 0
    }

    /// Whether the render loop should quit and terminate the application.
    #[inline]
    pub(crate) const fn should_quit(&self) -> bool {
        self.env.quit
    }

    /// Increment the internal frame counter. If the `show_frame_rate` option is set, update the
    /// title at most once every second.
    #[inline]
    pub(crate) fn increment_frame(&mut self, time_since_last: Duration) -> PixResult<()> {
        let s = &self.settings;
        let env = &mut self.env;

        if env.run_count > 0 {
            env.run_count -= 1;
        }
        env.frame_count += 1;

        if s.running && s.show_frame_rate {
            env.frame_timer += time_since_last;
            if env.frame_timer >= ONE_SECOND {
                env.frame_rate = env.frame_count as f32 / env.frame_timer.as_secs_f32();
                env.frame_timer -= ONE_SECOND;
                env.frame_count = 0;
                self.renderer.set_fps(env.frame_rate)?;
            }
        }

        Ok(())
    }

    /// Focus a given window.
    pub(crate) fn focus_window(&mut self, id: Option<WindowId>) {
        self.env.focused_window = id;
    }
}