1
use std::collections::HashMap;
2
use std::sync::{Arc, Mutex};
3

            
4
use kludgine_core::figures::num_traits::One;
5
use kludgine_core::figures::{Pixels, Point, Points};
6
use kludgine_core::flume;
7
use kludgine_core::math::{Scale, Scaled, Size};
8
use kludgine_core::scene::Target;
9
use kludgine_core::winit::window::{WindowBuilder as WinitWindowBuilder, WindowId};
10
use kludgine_core::winit::{self};
11
use lazy_static::lazy_static;
12

            
13
use crate::{Error, Runtime};
14

            
15
/// Types for event handling.
16
pub mod event;
17
mod open;
18
mod runtime_window;
19

            
20
pub use open::{OpenWindow, RedrawRequester, RedrawStatus};
21
pub use runtime_window::{opened_first_window, RuntimeWindow, RuntimeWindowConfig, WindowHandle};
22
pub use winit::window::{Icon, Theme, WindowLevel};
23

            
24
use self::event::InputEvent;
25

            
26
/// How to react to a request to close a window
27
pub enum CloseResponse {
28
    /// Window should remain open
29
    RemainOpen,
30
    /// Window should close
31
    Close,
32
}
33

            
34
/// Trait to implement a Window
35
pub trait Window: Send + Sync + 'static {
36
    /// Called when the window is being initilaized.
37
    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
    /// The window was requested to be closed, most likely from the Close
50
    /// Button. Override this implementation if you want logic in place to
51
    /// prevent a window from closing.
52
    fn close_requested(&mut self, _window: WindowHandle) -> crate::Result<CloseResponse> {
53
        Ok(CloseResponse::Close)
54
    }
55

            
56
    /// The window has received an input event.
57
    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
    /// A text input was received.
71
    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
    /// Specify a target frames per second, which will force your window
85
    /// to redraw at this rate. If None is returned, the Window will only
86
    /// redraw when requested via methods on Context.
87
    fn target_fps(&self) -> Option<u16> {
88
        None
89
    }
90

            
91
    /// Renders the contents of the window. Called whenever the operating system
92
    /// needs the window's contents to be redrawn or when [`RedrawStatus`]
93
    /// indicates a new frame should be rendered in [`Window::update()`].
94
    #[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
    /// Called on a regular basis as events come in. Use `status` to indicate
105
    /// when a redraw should happen.
106
    #[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
    /// Called prior to rendering to allow setting a scaling amount that
120
    /// operates on top of the automatic DPI scaling. This can be used to offer
121
    /// a zoom setting to end-users.
122
    fn additional_scale(&self) -> Scale<f32, Scaled, Points> {
123
        Scale::one()
124
    }
125
}
126

            
127
/// Defines initial window properties.
128
pub trait WindowCreator: Window {
129
    /// Returns a [`WindowBuilder`] for this window.
130
    #[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
    /// The initial title of the window.
151
    #[must_use]
152
    fn window_title(&self) -> String {
153
        "Kludgine".to_owned()
154
    }
155

            
156
    /// The initial position of the window. If None is returned, the operating
157
    /// system will position the window.
158
    #[must_use]
159
    fn initial_position(&self) -> Option<Point<i32, Pixels>> {
160
        None
161
    }
162

            
163
    /// The initial size of the window.
164
    #[must_use]
165
    fn initial_size(&self) -> Size<u32, Points> {
166
        Size::new(1024, 768)
167
    }
168

            
169
    /// Whether the window should be resizable or not.
170
    #[must_use]
171
    fn resizable(&self) -> bool {
172
        true
173
    }
174

            
175
    /// Whether the window should be maximized or not.
176
    #[must_use]
177
    fn maximized(&self) -> bool {
178
        false
179
    }
180

            
181
    /// Whether the window should be visible or not.
182
    #[must_use]
183
    fn visible(&self) -> bool {
184
        true
185
    }
186

            
187
    /// Whether the window should be transparent or not.
188
    #[must_use]
189
    fn transparent(&self) -> bool {
190
        false
191
    }
192

            
193
    /// Whether the window should be drawn with decorations or not.
194
    #[must_use]
195
    fn decorations(&self) -> bool {
196
        true
197
    }
198

            
199
    /// Whether the window should always be on top or not.
200
    #[must_use]
201
    fn window_level(&self) -> WindowLevel {
202
        WindowLevel::Normal
203
    }
204

            
205
    /// The default [`Theme`] for the [`Window`] if `winit` is unable to
206
    /// determine the system theme.
207
    #[must_use]
208
    fn initial_system_theme(&self) -> Theme {
209
        Theme::Light
210
    }
211
}
212

            
213
/// A builder for a [`Window`].
214
#[derive(Default)]
215
pub 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

            
229
impl WindowBuilder {
230
    /// Builder-style function. Sets `title` and returns self.
231
    #[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
    /// Builder-style function. Sets `size` and returns self.
238
    #[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
    /// Builder-style function. Sets `size` and returns self.
245
    #[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
    /// Builder-style function. Sets `resizable` and returns self.
252
    #[must_use]
253
    pub const fn with_resizable(mut self, resizable: bool) -> Self {
254
        self.resizable = Some(resizable);
255
        self
256
    }
257

            
258
    /// Builder-style function. Sets `maximized` and returns self.
259
    #[must_use]
260
    pub const fn with_maximized(mut self, maximized: bool) -> Self {
261
        self.maximized = Some(maximized);
262
        self
263
    }
264

            
265
    /// Builder-style function. Sets `visible` and returns self.
266
    #[must_use]
267
    pub const fn with_visible(mut self, visible: bool) -> Self {
268
        self.visible = Some(visible);
269
        self
270
    }
271

            
272
    /// Builder-style function. Sets `transparent` and returns self.
273
    #[must_use]
274
    pub const fn with_transparent(mut self, transparent: bool) -> Self {
275
        self.transparent = Some(transparent);
276
        self
277
    }
278

            
279
    /// Builder-style function. Sets `decorations` and returns self.
280
    #[must_use]
281
    pub const fn with_decorations(mut self, decorations: bool) -> Self {
282
        self.decorations = Some(decorations);
283
        self
284
    }
285

            
286
    /// Builder-style function. Sets `window_level` and returns self.
287
    #[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
    /// Builder-style function. Sets `icon` and returns self.
294
    #[must_use]
295
    #[allow(clippy::missing_const_for_fn)] // unsupported
296
    pub fn with_icon(mut self, icon: Icon) -> Self {
297
        self.icon = Some(icon);
298
        self
299
    }
300

            
301
    /// Builder-style function. Sets `initial_system_theme` and returns self.
302
    #[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

            
309
impl 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
/// A window that can be opened.
355
#[cfg(feature = "multiwindow")]
356
pub trait OpenableWindow {
357
    /// Opens `self` as a [`Window`].
358
    fn open(self);
359
}
360

            
361
#[cfg(feature = "multiwindow")]
362
impl<T> OpenableWindow for T
363
where
364
    T: Window + WindowCreator,
365
{
366
    fn open(self) {
367
        crate::runtime::Runtime::open_window(self.get_window_builder(), self);
368
    }
369
}
370

            
371
lazy_static! {
372
    static ref WINDOW_CHANNELS: Arc<Mutex<HashMap<WindowId, flume::Sender<WindowMessage>>>> =
373
        Arc::default();
374
}
375

            
376
pub enum WindowMessage {
377
    Close,
378
    RequestClose,
379
    SetAdditionalScale(Scale<f32, Scaled, Points>),
380
}
381

            
382
impl 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
}