goud_engine/libs/platform/mod.rs
1//! Platform abstraction layer.
2//!
3//! This module provides the [`PlatformBackend`] trait for windowing and input,
4//! enabling different platform implementations (GLFW, winit, SDL2) to be
5//! swapped without changing higher-level engine code.
6//!
7//! # Architecture
8//!
9//! ```text
10//! ┌──────────────────────────────┐
11//! │ PlatformBackend (trait) │
12//! ├──────────────────────────────┤
13//! │ GlfwPlatform │ WinitPlatform│ ← concrete implementations
14//! │ (desktop) │ (web+desk) │
15//! └──────────────────────────────┘
16//! ```
17//!
18//! # Usage
19//!
20//! ```rust,ignore
21//! use goud_engine::libs::platform::{PlatformBackend, WindowConfig};
22//! use goud_engine::libs::platform::glfw_platform::GlfwPlatform;
23//!
24//! let config = WindowConfig {
25//! width: 800,
26//! height: 600,
27//! title: "My Game".to_string(),
28//! ..Default::default()
29//! };
30//! let mut platform = GlfwPlatform::new(&config)?;
31//! ```
32
33#[cfg(feature = "native")]
34pub mod glfw_platform;
35#[cfg(all(feature = "wgpu-backend", feature = "native"))]
36pub mod winit_platform;
37
38#[cfg(feature = "native")]
39use crate::core::input_manager::InputManager;
40
41/// Configuration for creating a platform window.
42#[derive(Debug, Clone)]
43pub struct WindowConfig {
44 /// Window width in pixels.
45 pub width: u32,
46
47 /// Window height in pixels.
48 pub height: u32,
49
50 /// Window title displayed in the title bar.
51 pub title: String,
52
53 /// Enable vertical sync to prevent screen tearing.
54 pub vsync: bool,
55
56 /// Allow the user to resize the window.
57 pub resizable: bool,
58}
59
60impl Default for WindowConfig {
61 fn default() -> Self {
62 Self {
63 width: 800,
64 height: 600,
65 title: "GoudEngine".to_string(),
66 vsync: true,
67 resizable: true,
68 }
69 }
70}
71
72/// Platform backend abstraction for window and input management.
73///
74/// Implementations handle platform-specific window lifecycle, event polling,
75/// input dispatch, and buffer presentation. This trait enables the engine to
76/// support multiple windowing systems through a unified interface.
77///
78/// # Lifecycle
79///
80/// 1. Create the backend via its constructor (e.g., `GlfwPlatform::new(config)`)
81/// 2. Each frame: call [`poll_events`](PlatformBackend::poll_events) → render → call [`swap_buffers`](PlatformBackend::swap_buffers)
82/// 3. Check [`should_close`](PlatformBackend::should_close) to determine when to exit
83///
84/// # Thread Safety
85///
86/// Most windowing APIs require main-thread access. Implementations are NOT
87/// required to be `Send` or `Sync`.
88#[cfg(feature = "native")]
89pub trait PlatformBackend {
90 /// Returns `true` if the window has been requested to close.
91 fn should_close(&self) -> bool;
92
93 /// Sets whether the window should close.
94 fn set_should_close(&mut self, should_close: bool);
95
96 /// Polls platform events and feeds input events to the [`InputManager`].
97 ///
98 /// This advances the input state for the new frame, processes all pending
99 /// platform events, and calculates the time elapsed since the last call.
100 ///
101 /// Returns delta time in seconds since the last call.
102 fn poll_events(&mut self, input: &mut InputManager) -> f32;
103
104 /// Presents the rendered frame by swapping front and back buffers.
105 fn swap_buffers(&mut self);
106
107 /// Returns the logical window size `(width, height)` in screen coordinates.
108 fn get_size(&self) -> (u32, u32);
109
110 /// Returns the physical framebuffer size `(width, height)` in pixels.
111 ///
112 /// This may differ from [`get_size`](PlatformBackend::get_size) on
113 /// HiDPI/Retina displays where the framebuffer resolution is higher
114 /// than the logical window size.
115 fn get_framebuffer_size(&self) -> (u32, u32);
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn window_config_default_values() {
124 let config = WindowConfig::default();
125 assert_eq!(config.width, 800);
126 assert_eq!(config.height, 600);
127 assert_eq!(config.title, "GoudEngine");
128 assert!(config.vsync);
129 assert!(config.resizable);
130 }
131
132 #[test]
133 fn window_config_clone() {
134 let config = WindowConfig {
135 width: 1920,
136 height: 1080,
137 title: "Test".to_string(),
138 vsync: false,
139 resizable: false,
140 };
141 let cloned = config.clone();
142 assert_eq!(config.width, cloned.width);
143 assert_eq!(config.height, cloned.height);
144 assert_eq!(config.title, cloned.title);
145 assert_eq!(config.vsync, cloned.vsync);
146 assert_eq!(config.resizable, cloned.resizable);
147 }
148}