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}