1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use kludgine_core::figures::num_traits::One;
5use kludgine_core::figures::{Pixels, Point, Points};
6use kludgine_core::flume;
7use kludgine_core::math::{Scale, Scaled, Size};
8use kludgine_core::scene::Target;
9use kludgine_core::winit::window::{WindowBuilder as WinitWindowBuilder, WindowId};
10use kludgine_core::winit::{self};
11use lazy_static::lazy_static;
12
13use crate::{Error, Runtime};
14
15pub mod event;
17mod open;
18mod runtime_window;
19
20pub use open::{OpenWindow, RedrawRequester, RedrawStatus};
21pub use runtime_window::{opened_first_window, RuntimeWindow, RuntimeWindowConfig, WindowHandle};
22pub use winit::window::{Icon, Theme, WindowLevel};
23
24use self::event::InputEvent;
25
26pub enum CloseResponse {
28 RemainOpen,
30 Close,
32}
33
34pub trait Window: Send + Sync + 'static {
36 fn initialize(
38 &mut self,
39 _scene: &Target,
40 _redrawer: RedrawRequester,
41 _window: WindowHandle,
42 ) -> crate::Result<()>
43 where
44 Self: Sized,
45 {
46 Ok(())
47 }
48
49 fn close_requested(&mut self, _window: WindowHandle) -> crate::Result<CloseResponse> {
53 Ok(CloseResponse::Close)
54 }
55
56 fn process_input(
58 &mut self,
59 _input: InputEvent,
60 _status: &mut RedrawStatus,
61 _scene: &Target,
62 _window: WindowHandle,
63 ) -> crate::Result<()>
64 where
65 Self: Sized,
66 {
67 Ok(())
68 }
69
70 fn receive_character(
72 &mut self,
73 _character: char,
74 _status: &mut RedrawStatus,
75 _scene: &Target,
76 _window: WindowHandle,
77 ) -> crate::Result<()>
78 where
79 Self: Sized,
80 {
81 Ok(())
82 }
83
84 fn target_fps(&self) -> Option<u16> {
88 None
89 }
90
91 #[allow(unused_variables)]
95 fn render(
96 &mut self,
97 scene: &Target,
98 status: &mut RedrawStatus,
99 _window: WindowHandle,
100 ) -> crate::Result<()> {
101 Ok(())
102 }
103
104 #[allow(unused_variables)]
107 fn update(
108 &mut self,
109 scene: &Target,
110 status: &mut RedrawStatus,
111 _window: WindowHandle,
112 ) -> crate::Result<()>
113 where
114 Self: Sized,
115 {
116 Ok(())
117 }
118
119 fn additional_scale(&self) -> Scale<f32, Scaled, Points> {
123 Scale::one()
124 }
125}
126
127pub trait WindowCreator: Window {
129 #[must_use]
131 fn get_window_builder(&self) -> WindowBuilder {
132 let mut builder = WindowBuilder::default()
133 .with_title(self.window_title())
134 .with_initial_system_theme(self.initial_system_theme())
135 .with_size(self.initial_size())
136 .with_resizable(self.resizable())
137 .with_maximized(self.maximized())
138 .with_visible(self.visible())
139 .with_transparent(self.transparent())
140 .with_decorations(self.decorations())
141 .with_window_level(self.window_level());
142
143 if let Some(position) = self.initial_position() {
144 builder = builder.with_position(position);
145 }
146
147 builder
148 }
149
150 #[must_use]
152 fn window_title(&self) -> String {
153 "Kludgine".to_owned()
154 }
155
156 #[must_use]
159 fn initial_position(&self) -> Option<Point<i32, Pixels>> {
160 None
161 }
162
163 #[must_use]
165 fn initial_size(&self) -> Size<u32, Points> {
166 Size::new(1024, 768)
167 }
168
169 #[must_use]
171 fn resizable(&self) -> bool {
172 true
173 }
174
175 #[must_use]
177 fn maximized(&self) -> bool {
178 false
179 }
180
181 #[must_use]
183 fn visible(&self) -> bool {
184 true
185 }
186
187 #[must_use]
189 fn transparent(&self) -> bool {
190 false
191 }
192
193 #[must_use]
195 fn decorations(&self) -> bool {
196 true
197 }
198
199 #[must_use]
201 fn window_level(&self) -> WindowLevel {
202 WindowLevel::Normal
203 }
204
205 #[must_use]
208 fn initial_system_theme(&self) -> Theme {
209 Theme::Light
210 }
211}
212
213#[derive(Default)]
215pub struct WindowBuilder {
216 title: Option<String>,
217 position: Option<Point<i32, Pixels>>,
218 size: Option<Size<u32, Points>>,
219 resizable: Option<bool>,
220 maximized: Option<bool>,
221 visible: Option<bool>,
222 transparent: Option<bool>,
223 decorations: Option<bool>,
224 window_level: Option<WindowLevel>,
225 pub(crate) initial_system_theme: Option<Theme>,
226 icon: Option<winit::window::Icon>,
227}
228
229impl WindowBuilder {
230 #[must_use]
232 pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
233 self.title = Some(title.into());
234 self
235 }
236
237 #[must_use]
239 pub const fn with_position(mut self, position: Point<i32, Pixels>) -> Self {
240 self.position = Some(position);
241 self
242 }
243
244 #[must_use]
246 pub const fn with_size(mut self, size: Size<u32, Points>) -> Self {
247 self.size = Some(size);
248 self
249 }
250
251 #[must_use]
253 pub const fn with_resizable(mut self, resizable: bool) -> Self {
254 self.resizable = Some(resizable);
255 self
256 }
257
258 #[must_use]
260 pub const fn with_maximized(mut self, maximized: bool) -> Self {
261 self.maximized = Some(maximized);
262 self
263 }
264
265 #[must_use]
267 pub const fn with_visible(mut self, visible: bool) -> Self {
268 self.visible = Some(visible);
269 self
270 }
271
272 #[must_use]
274 pub const fn with_transparent(mut self, transparent: bool) -> Self {
275 self.transparent = Some(transparent);
276 self
277 }
278
279 #[must_use]
281 pub const fn with_decorations(mut self, decorations: bool) -> Self {
282 self.decorations = Some(decorations);
283 self
284 }
285
286 #[must_use]
288 pub const fn with_window_level(mut self, level: WindowLevel) -> Self {
289 self.window_level = Some(level);
290 self
291 }
292
293 #[must_use]
295 #[allow(clippy::missing_const_for_fn)] pub fn with_icon(mut self, icon: Icon) -> Self {
297 self.icon = Some(icon);
298 self
299 }
300
301 #[must_use]
303 pub const fn with_initial_system_theme(mut self, system_theme: Theme) -> Self {
304 self.initial_system_theme = Some(system_theme);
305 self
306 }
307}
308
309impl From<WindowBuilder> for WinitWindowBuilder {
310 fn from(wb: WindowBuilder) -> Self {
311 let mut builder = Self::new();
312 if let Some(title) = wb.title {
313 builder = builder.with_title(title);
314 }
315 if let Some(position) = wb.position {
316 builder = builder.with_position(winit::dpi::Position::Physical(
317 winit::dpi::PhysicalPosition {
318 x: position.x,
319 y: position.y,
320 },
321 ));
322 }
323 if let Some(size) = wb.size {
324 builder = builder.with_inner_size(winit::dpi::Size::Logical(winit::dpi::LogicalSize {
325 width: f64::from(size.width),
326 height: f64::from(size.height),
327 }));
328 }
329 if let Some(resizable) = wb.resizable {
330 builder = builder.with_resizable(resizable);
331 }
332 if let Some(maximized) = wb.maximized {
333 builder = builder.with_maximized(maximized);
334 }
335 if let Some(visible) = wb.visible {
336 builder = builder.with_visible(visible);
337 }
338 if let Some(transparent) = wb.transparent {
339 builder = builder.with_transparent(transparent);
340 }
341 if let Some(decorations) = wb.decorations {
342 builder = builder.with_decorations(decorations);
343 }
344 if let Some(level) = wb.window_level {
345 builder = builder.with_window_level(level);
346 }
347
348 builder = builder.with_window_icon(wb.icon);
349
350 builder
351 }
352}
353
354#[cfg(feature = "multiwindow")]
356pub trait OpenableWindow {
357 fn open(self);
359}
360
361#[cfg(feature = "multiwindow")]
362impl<T> OpenableWindow for T
363where
364 T: Window + WindowCreator,
365{
366 fn open(self) {
367 crate::runtime::Runtime::open_window(self.get_window_builder(), self);
368 }
369}
370
371lazy_static! {
372 static ref WINDOW_CHANNELS: Arc<Mutex<HashMap<WindowId, flume::Sender<WindowMessage>>>> =
373 Arc::default();
374}
375
376pub enum WindowMessage {
377 Close,
378 RequestClose,
379 SetAdditionalScale(Scale<f32, Scaled, Points>),
380}
381
382impl WindowMessage {
383 pub fn send_to(self, id: WindowId) -> crate::Result<()> {
384 let sender = {
385 let mut channels = WINDOW_CHANNELS.lock().unwrap();
386 if let Some(sender) = channels.get_mut(&id) {
387 sender.clone()
388 } else {
389 return Err(Error::InternalWindowMessageSend(
390 "Channel not found for id".to_owned(),
391 ));
392 }
393 };
394
395 sender.send(self).unwrap_or_default();
396 Runtime::try_process_window_events(None);
397 Ok(())
398 }
399}