1use std::sync::Arc;
6
7use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
8use winit::dpi::{LogicalSize, PhysicalSize};
9use winit::window::WindowAttributes;
10
11use crate::Result;
12
13#[derive(Debug, Clone)]
15pub struct WindowConfig {
16 pub title: String,
18 pub width: u32,
20 pub height: u32,
22 pub resizable: bool,
24 pub vsync: bool,
26 pub fullscreen: bool,
28 pub min_size: Option<(u32, u32)>,
30 pub max_size: Option<(u32, u32)>,
32}
33
34impl Default for WindowConfig {
35 fn default() -> Self {
36 Self {
37 title: "Sable Engine".to_string(),
38 width: 1280,
39 height: 720,
40 resizable: true,
41 vsync: true,
42 fullscreen: false,
43 min_size: Some((320, 240)),
44 max_size: None,
45 }
46 }
47}
48
49impl WindowConfig {
50 #[must_use]
52 pub fn new(title: impl Into<String>) -> Self {
53 Self {
54 title: title.into(),
55 ..Default::default()
56 }
57 }
58
59 #[must_use]
61 pub fn with_size(mut self, width: u32, height: u32) -> Self {
62 self.width = width;
63 self.height = height;
64 self
65 }
66
67 #[must_use]
69 pub fn with_resizable(mut self, resizable: bool) -> Self {
70 self.resizable = resizable;
71 self
72 }
73
74 #[must_use]
76 pub fn with_vsync(mut self, vsync: bool) -> Self {
77 self.vsync = vsync;
78 self
79 }
80
81 #[must_use]
83 pub fn with_fullscreen(mut self, fullscreen: bool) -> Self {
84 self.fullscreen = fullscreen;
85 self
86 }
87
88 #[must_use]
90 pub fn with_min_size(mut self, width: u32, height: u32) -> Self {
91 self.min_size = Some((width, height));
92 self
93 }
94
95 #[must_use]
97 pub fn with_max_size(mut self, width: u32, height: u32) -> Self {
98 self.max_size = Some((width, height));
99 self
100 }
101}
102
103#[derive(Debug, Clone)]
107pub struct Window {
108 inner: Arc<winit::window::Window>,
109 vsync: bool,
110}
111
112impl Window {
113 pub fn new(event_loop: &winit::event_loop::ActiveEventLoop, config: &WindowConfig) -> Result<Self> {
119 let mut attributes = WindowAttributes::default()
120 .with_title(&config.title)
121 .with_inner_size(LogicalSize::new(config.width, config.height))
122 .with_resizable(config.resizable);
123
124 if let Some((min_w, min_h)) = config.min_size {
125 attributes = attributes.with_min_inner_size(LogicalSize::new(min_w, min_h));
126 }
127
128 if let Some((max_w, max_h)) = config.max_size {
129 attributes = attributes.with_max_inner_size(LogicalSize::new(max_w, max_h));
130 }
131
132 if config.fullscreen {
133 attributes =
134 attributes.with_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
135 }
136
137 let window = event_loop.create_window(attributes)?;
138
139 Ok(Self {
140 inner: Arc::new(window),
141 vsync: config.vsync,
142 })
143 }
144
145 #[must_use]
147 pub fn inner_size(&self) -> PhysicalSize<u32> {
148 self.inner.inner_size()
149 }
150
151 #[must_use]
153 pub fn scale_factor(&self) -> f64 {
154 self.inner.scale_factor()
155 }
156
157 #[must_use]
159 pub fn vsync(&self) -> bool {
160 self.vsync
161 }
162
163 pub fn set_title(&self, title: &str) {
165 self.inner.set_title(title);
166 }
167
168 pub fn request_redraw(&self) {
170 self.inner.request_redraw();
171 }
172
173 pub fn window_handle(
179 &self,
180 ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError>
181 {
182 self.inner.window_handle()
183 }
184
185 pub fn display_handle(
191 &self,
192 ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError>
193 {
194 self.inner.display_handle()
195 }
196
197 #[must_use]
201 pub fn winit_window(&self) -> &winit::window::Window {
202 &self.inner
203 }
204
205 #[must_use]
207 pub fn id(&self) -> winit::window::WindowId {
208 self.inner.id()
209 }
210}
211
212unsafe impl Send for Window {}
214unsafe impl Sync for Window {}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_window_config_default() {
222 let config = WindowConfig::default();
223 assert_eq!(config.title, "Sable Engine");
224 assert_eq!(config.width, 1280);
225 assert_eq!(config.height, 720);
226 assert!(config.resizable);
227 assert!(config.vsync);
228 assert!(!config.fullscreen);
229 assert_eq!(config.min_size, Some((320, 240)));
230 assert_eq!(config.max_size, None);
231 }
232
233 #[test]
234 fn test_window_config_builder() {
235 let config = WindowConfig::new("Test Window")
236 .with_size(800, 600)
237 .with_resizable(false)
238 .with_vsync(false)
239 .with_fullscreen(true)
240 .with_min_size(400, 300)
241 .with_max_size(1920, 1080);
242
243 assert_eq!(config.title, "Test Window");
244 assert_eq!(config.width, 800);
245 assert_eq!(config.height, 600);
246 assert!(!config.resizable);
247 assert!(!config.vsync);
248 assert!(config.fullscreen);
249 assert_eq!(config.min_size, Some((400, 300)));
250 assert_eq!(config.max_size, Some((1920, 1080)));
251 }
252
253 #[test]
254 fn test_window_config_into_string() {
255 let config = WindowConfig::new(String::from("Dynamic Title"));
256 assert_eq!(config.title, "Dynamic Title");
257 }
258}
259