kludgine_app/
window.rs

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
15/// Types for event handling.
16pub 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
26/// How to react to a request to close a window
27pub enum CloseResponse {
28    /// Window should remain open
29    RemainOpen,
30    /// Window should close
31    Close,
32}
33
34/// Trait to implement a Window
35pub 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.
128pub 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)]
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    /// 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
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/// A window that can be opened.
355#[cfg(feature = "multiwindow")]
356pub trait OpenableWindow {
357    /// Opens `self` as a [`Window`].
358    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}