pix_engine/state/
environment.rs

1//! Environment methods for the [`Engine`].
2//!
3//! Methods for reading and setting various engine environment values.
4//!
5//! Provided [`PixState`] methods:
6//!
7//! - [`PixState::focused`]: Whether the current window target has focus.
8//! - [`PixState::delta_time`]: [Duration] elapsed since last frame.
9//! - [`PixState::elapsed`]: [Duration] elapsed since application start.
10//! - [`PixState::frame_count`]: Total number of frames since application start.
11//! - [`PixState::redraw`]: Run render loop 1 time, calling [`PixEngine::on_update`].
12//! - [`PixState::run_times`]: Run render loop N times, calling [`PixEngine::on_update`].
13//! - [`PixState::avg_frame_rate`]: Average frames per second rendered.
14//! - [`PixState::quit`]: Trigger application quit.
15//! - [`PixState::abort_quit`]: Abort application quit.
16//! - [`PixState::day`]: Return the current day between 1-31.
17//! - [`PixState::month`]: Return the current month between 1-12.
18//! - [`PixState::year`]: Return the current year as an integer.
19//! - [`PixState::hour`]: Return the current hour between 0-23.
20//! - [`PixState::minute`]: Return the current minute between 0-59.
21//! - [`PixState::second`]: Return the current second between 0-59.
22
23use crate::{
24    prelude::*,
25    renderer::{Rendering, WindowRenderer},
26};
27use std::time::{Duration, Instant};
28use time::OffsetDateTime;
29
30const ONE_SECOND: Duration = Duration::from_secs(1);
31
32/// Environment values for [`PixState`]
33#[derive(Debug, Clone)]
34pub(crate) struct Environment {
35    focused_window: Option<WindowId>,
36    delta_time: Duration,
37    start: Instant,
38    frame_rate: f32,
39    frame_count: usize,
40    run_count: usize,
41    quit: bool,
42    last_frame_time: Instant,
43    frame_timer: Duration,
44}
45
46impl Default for Environment {
47    fn default() -> Self {
48        Self {
49            focused_window: None,
50            delta_time: Duration::default(),
51            start: Instant::now(),
52            frame_rate: 0.0,
53            frame_count: 0,
54            run_count: 0,
55            quit: false,
56            last_frame_time: Instant::now(),
57            frame_timer: Duration::default(),
58        }
59    }
60}
61
62impl PixState {
63    /// Present all renderer changes since last frame.
64    #[inline]
65    pub fn present(&mut self) {
66        self.renderer.present();
67    }
68
69    /// Returns whether the current window target has focus. To check focus of a specific window, see
70    /// [`PixState::focused_window`].
71    ///
72    /// # Example
73    ///
74    /// ```
75    /// # use pix_engine::prelude::*;
76    /// # struct App;
77    /// # impl PixEngine for App {
78    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
79    ///     if s.focused() {
80    ///         // Update screen only when focused
81    ///         s.rect([0, 0, 100, 100])?;
82    ///     }
83    ///     Ok(())
84    /// }
85    /// # }
86    /// ```
87    #[inline]
88    #[must_use]
89    pub fn focused(&self) -> bool {
90        self.env
91            .focused_window
92            .map(|focused_window| focused_window == self.renderer.window_id())
93            .unwrap_or_default()
94    }
95
96    /// Returns whether a given window has focus. To check focus of a any window, see
97    /// [`PixState::focused`].
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// # use pix_engine::prelude::*;
103    /// # struct App;
104    /// # impl PixEngine for App {
105    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
106    ///     if s.focused_window(s.window_id()) {
107    ///         // Update screen only when focused
108    ///         s.rect([0, 0, 100, 100])?;
109    ///     }
110    ///     Ok(())
111    /// }
112    /// # }
113    /// ```
114    #[inline]
115    #[must_use]
116    pub fn focused_window(&self, window_id: WindowId) -> bool {
117        matches!(self.env.focused_window, Some(id) if id == window_id)
118    }
119
120    /// The [Duration] elapsed since last frame.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// # use pix_engine::prelude::*;
126    /// # struct App { position: f64, velocity: f64 };
127    /// # impl PixEngine for App {
128    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
129    ///     // Update position based on frame timestep
130    ///     self.position = self.velocity * s.delta_time().as_secs_f64();
131    ///     Ok(())
132    /// }
133    /// # }
134    /// ```
135    #[inline]
136    #[must_use]
137    pub const fn delta_time(&self) -> Duration {
138        self.env.delta_time
139    }
140
141    /// The [Duration[ elapsed since application start.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// # use pix_engine::prelude::*;
147    /// # struct App;
148    /// # impl PixEngine for App {
149    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
150    ///     // Draw a blinking box, indepdendent of frame rate
151    ///     if s.elapsed().as_millis() >> 9 & 1 > 0 {
152    ///         s.rect([0, 0, 10, 10])?;
153    ///     }
154    ///     Ok(())
155    /// }
156    /// # }
157    /// ```
158    #[inline]
159    #[must_use]
160    pub fn elapsed(&self) -> Duration {
161        self.env.start.elapsed()
162    }
163
164    /// The total number of frames rendered since application start.
165    ///
166    /// # Example
167    ///
168    /// ```
169    /// # use pix_engine::prelude::*;
170    /// # struct App;
171    /// # impl PixEngine for App {
172    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
173    ///     // Create a strobe effect, dependent on frame rate
174    ///     if s.frame_count() % 5 == 0 {
175    ///         s.rect([0, 0, 10, 10])?;
176    ///     }
177    ///     Ok(())
178    /// }
179    /// # }
180    /// ```
181    #[inline]
182    #[must_use]
183    pub const fn frame_count(&self) -> usize {
184        self.env.frame_count
185    }
186
187    /// Run the render loop 1 time by calling [`PixEngine::on_update`].
188    ///
189    /// This can be used to only redraw in response to user actions such as
190    /// [`PixEngine::on_mouse_pressed`] or [`PixEngine::on_key_pressed`].
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// # use pix_engine::prelude::*;
196    /// # struct App;
197    /// # impl PixEngine for App {
198    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
199    /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
200    ///     s.run(false); // Disable render loop
201    ///     Ok(())
202    /// }
203    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
204    ///     // Step one frame draw at a time on each space press
205    ///     // Useful for debugging frame-by-frame
206    ///     if let Key::Space = event.key {
207    ///         s.redraw();
208    ///         return Ok(true);
209    ///     }
210    ///     Ok(false)
211    /// # }
212    /// # }
213    /// ```
214    #[inline]
215    pub fn redraw(&mut self) {
216        self.env.run_count = 1;
217    }
218
219    /// Run the render loop N times by calling [`PixEngine::on_update`].
220    ///
221    /// This can be used to only redraw in response to user actions such as
222    /// [`PixEngine::on_mouse_pressed`] or [`PixEngine::on_key_pressed`].
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// # use pix_engine::prelude::*;
228    /// # struct App;
229    /// # impl PixEngine for App {
230    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
231    /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
232    ///     s.run(false); // Disable render loop
233    ///     Ok(())
234    /// }
235    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
236    ///     // Step one frame draw at a time on each space press
237    ///     // Useful for debugging by multiple frames at a time
238    ///     if let Key::Space = event.key {
239    ///         s.run_times(4);
240    ///         return Ok(true);
241    ///     }
242    ///     Ok(false)
243    /// # }
244    /// # }
245    /// ```
246    #[inline]
247    pub fn run_times(&mut self, n: usize) {
248        self.env.run_count = n;
249    }
250
251    /// The average frames per second rendered.
252    ///
253    /// # Example
254    ///
255    /// ```
256    /// # use pix_engine::prelude::*;
257    /// # struct App;
258    /// # impl PixEngine for App {
259    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
260    ///     s.text(format!("FPS: {}", s.avg_frame_rate()))?;
261    ///     Ok(())
262    /// # }
263    /// # }
264    /// ```
265    #[inline]
266    #[must_use]
267    pub const fn avg_frame_rate(&self) -> f32 {
268        self.env.frame_rate
269    }
270
271    /// Trigger application quit.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// # use pix_engine::prelude::*;
277    /// # struct App;
278    /// # impl PixEngine for App {
279    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
280    ///     if s.button("Quit?")? {
281    ///         s.quit();
282    ///     }
283    ///     Ok(())
284    /// # }
285    /// # }
286    /// ```
287    #[inline]
288    pub fn quit(&mut self) {
289        self.env.quit = true;
290    }
291
292    /// Abort application quit and resume render loop by calling [`PixEngine::on_update`].
293    ///
294    /// # Example
295    ///
296    /// ```
297    /// # use pix_engine::prelude::*;
298    /// # struct App { has_unsaved_changes: bool, prompt_save_dialog: bool }
299    /// # impl PixEngine for App {
300    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
301    /// fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
302    ///     if self.has_unsaved_changes {
303    ///         self.prompt_save_dialog = true;
304    ///         s.abort_quit();
305    ///     }
306    ///     Ok(())
307    /// # }
308    /// # }
309    /// ```
310    #[inline]
311    pub fn abort_quit(&mut self) {
312        self.env.quit = false;
313    }
314
315    /// Return the current day between 1-31.
316    #[inline]
317    #[must_use]
318    pub fn day() -> u32 {
319        OffsetDateTime::now_local()
320            .unwrap_or(OffsetDateTime::now_utc())
321            .day() as u32
322    }
323
324    /// Return the current month between 1-12.
325    #[inline]
326    #[must_use]
327    pub fn month() -> u32 {
328        OffsetDateTime::now_local()
329            .unwrap_or(OffsetDateTime::now_utc())
330            .month() as u32
331    }
332
333    /// Return the current year as an integer.
334    #[inline]
335    #[must_use]
336    pub fn year() -> i32 {
337        OffsetDateTime::now_local()
338            .unwrap_or(OffsetDateTime::now_utc())
339            .year()
340    }
341
342    /// Return the current hour between 0-23.
343    #[inline]
344    #[must_use]
345    pub fn hour() -> u32 {
346        OffsetDateTime::now_local()
347            .unwrap_or(OffsetDateTime::now_utc())
348            .hour() as u32
349    }
350
351    /// Return the current minute between 0-59.
352    #[inline]
353    #[must_use]
354    pub fn minute() -> u32 {
355        OffsetDateTime::now_local()
356            .unwrap_or(OffsetDateTime::now_utc())
357            .minute() as u32
358    }
359
360    /// Return the current second between 0-59.
361    #[inline]
362    #[must_use]
363    pub fn second() -> u32 {
364        OffsetDateTime::now_local()
365            .unwrap_or(OffsetDateTime::now_utc())
366            .second() as u32
367    }
368}
369
370impl PixState {
371    /// Return the instant the last frame was rendered at.
372    #[inline]
373    pub(crate) const fn last_frame_time(&self) -> Instant {
374        self.env.last_frame_time
375    }
376
377    /// Set the delta time since last frame.
378    #[inline]
379    pub(crate) fn set_delta_time(&mut self, now: Instant, time_since_last: Duration) {
380        self.env.delta_time = time_since_last;
381        self.env.last_frame_time = now;
382    }
383
384    /// Whether the current render loop should be running or not.
385    #[inline]
386    pub(crate) const fn is_running(&self) -> bool {
387        self.settings.running || self.env.run_count > 0
388    }
389
390    /// Whether the render loop should quit and terminate the application.
391    #[inline]
392    pub(crate) const fn should_quit(&self) -> bool {
393        self.env.quit
394    }
395
396    /// Increment the internal frame counter. If the `show_frame_rate` option is set, update the
397    /// title at most once every second.
398    #[inline]
399    pub(crate) fn increment_frame(&mut self, time_since_last: Duration) -> PixResult<()> {
400        let s = &self.settings;
401        let env = &mut self.env;
402
403        if env.run_count > 0 {
404            env.run_count -= 1;
405        }
406        env.frame_count += 1;
407
408        if s.running && s.show_frame_rate {
409            env.frame_timer += time_since_last;
410            if env.frame_timer >= ONE_SECOND {
411                env.frame_rate = env.frame_count as f32 / env.frame_timer.as_secs_f32();
412                env.frame_timer -= ONE_SECOND;
413                env.frame_count = 0;
414                self.renderer.set_fps(env.frame_rate)?;
415            }
416        }
417
418        Ok(())
419    }
420
421    /// Focus a given window.
422    pub(crate) fn focus_window(&mut self, id: Option<WindowId>) {
423        self.env.focused_window = id;
424    }
425}