kludgine_app/window/
runtime_window.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use kludgine_core::easygpu::prelude::*;
6use kludgine_core::figures::num_traits::One;
7use kludgine_core::figures::{DisplayScale, Displayable, Pixels};
8use kludgine_core::math::{Point, Scale, Size};
9use kludgine_core::scene::{Scene, SceneEvent};
10use kludgine_core::winit::dpi::{PhysicalPosition, PhysicalSize};
11use kludgine_core::winit::event::WindowEvent as WinitWindowEvent;
12use kludgine_core::winit::window::{Theme, WindowId, WindowLevel};
13use kludgine_core::winit::{self};
14use kludgine_core::{flume, FrameRenderer};
15use once_cell::sync::OnceCell;
16use tracing::instrument;
17
18use super::OpenWindow;
19use crate::runtime::{Runtime, RuntimeRequest, WINDOWS};
20use crate::window::event::{ElementState, Event, InputEvent, VirtualKeyCode, WindowEvent};
21use crate::window::{CloseResponse, Window, WindowMessage, WINDOW_CHANNELS};
22use crate::Error;
23
24pub struct RuntimeWindow {
25    pub window_id: WindowId,
26    pub keep_running: Arc<AtomicBool>,
27    receiver: flume::Receiver<WindowMessage>,
28    event_sender: flume::Sender<WindowEvent>,
29    last_known_size: Size<u32, Pixels>,
30    last_known_scale_factor: DisplayScale<f32>,
31}
32
33pub struct RuntimeWindowConfig {
34    window_id: WindowId,
35    instance: wgpu::Instance,
36    surface: wgpu::Surface,
37    initial_size: Size<u32, Pixels>,
38    scale_factor: f32,
39}
40
41impl RuntimeWindowConfig {
42    pub fn new(window: &winit::window::Window) -> Result<Self, wgpu::CreateSurfaceError> {
43        // TODO in wasm, we need to explicity enable GL, but since wasm isn't possible
44        // right now, we're just hardcoding primary
45        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
46            backends: wgpu::Backends::PRIMARY,
47            dx12_shader_compiler: wgpu::Dx12Compiler::default(),
48        });
49        let surface = unsafe { instance.create_surface(window) }?;
50        Ok(Self {
51            window_id: window.id(),
52            instance,
53            surface,
54            initial_size: Size::new(window.inner_size().width, window.inner_size().height),
55            scale_factor: window.scale_factor() as f32,
56        })
57    }
58}
59
60static OPENED_FIRST_WINDOW: OnceCell<()> = OnceCell::new();
61
62pub fn opened_first_window() -> bool {
63    OPENED_FIRST_WINDOW.get().is_some()
64}
65
66fn set_opened_first_window() {
67    OPENED_FIRST_WINDOW.get_or_init(|| ());
68}
69
70#[cfg(not(target_arch = "wasm32"))]
71type Format = kludgine_core::sprite::Srgb;
72#[cfg(target_arch = "wasm32")]
73type Format = kludgine_core::sprite::Normal;
74
75impl RuntimeWindow {
76    pub(crate) fn open<T>(
77        window_receiver: &flume::Receiver<RuntimeWindowConfig>,
78        initial_system_theme: Theme,
79        app_window: T,
80    ) where
81        T: Window + Sized + 'static,
82    {
83        let RuntimeWindowConfig {
84            window_id,
85            instance,
86            surface,
87            initial_size,
88            scale_factor,
89        } = window_receiver
90            .recv()
91            .expect("Error receiving winit::window");
92
93        let (message_sender, message_receiver) = flume::unbounded();
94        let (event_sender, event_receiver) = flume::unbounded();
95
96        let keep_running = Arc::new(AtomicBool::new(true));
97        let task_keep_running = keep_running.clone();
98        let window_event_sender = event_sender.clone();
99        let (scene_event_sender, scene_event_receiver) = flume::unbounded();
100
101        // Each window has its own thread/task operating its core update/render
102        // lifecycle.
103        std::thread::Builder::new()
104            .name(String::from("kludgine-window-loop"))
105            .spawn(move || {
106                Self::window_main(
107                    window_id,
108                    scene_event_sender,
109                    window_event_sender,
110                    event_receiver,
111                    initial_system_theme,
112                    app_window,
113                );
114            })
115            .unwrap();
116
117        let renderer = Runtime::block_on(async move {
118            Renderer::for_surface(surface, &instance, 4)
119                .await
120                .expect("Error creating renderer for window")
121        });
122
123        FrameRenderer::<Format>::run(renderer, task_keep_running, scene_event_receiver, || {
124            RuntimeRequest::WindowClosed.send();
125        });
126
127        {
128            let mut channels = WINDOW_CHANNELS.lock().unwrap();
129            channels.insert(window_id, message_sender);
130        }
131
132        let mut runtime_window = Self {
133            receiver: message_receiver,
134            last_known_size: initial_size,
135            keep_running,
136            event_sender,
137            last_known_scale_factor: DisplayScale::new(Scale::new(scale_factor), Scale::one()),
138            window_id,
139        };
140        runtime_window.notify_size_changed();
141
142        let mut windows = WINDOWS.write();
143        windows.insert(window_id, runtime_window);
144
145        set_opened_first_window();
146    }
147
148    fn request_window_close<T>(id: WindowId, window: &mut OpenWindow<T>) -> crate::Result<bool>
149    where
150        T: Window,
151    {
152        if let CloseResponse::RemainOpen = window.request_close()? {
153            return Ok(false);
154        }
155
156        WindowMessage::Close.send_to(id)?;
157        Ok(true)
158    }
159
160    fn next_window_event_non_blocking(
161        event_receiver: &mut flume::Receiver<WindowEvent>,
162    ) -> crate::Result<Option<WindowEvent>> {
163        match event_receiver.try_recv() {
164            Ok(event) => Ok(Some(event)),
165            Err(flume::TryRecvError::Empty) => Ok(None),
166            Err(flume::TryRecvError::Disconnected) => Err(Error::InternalWindowMessageSend(
167                "Window channel closed".to_owned(),
168            )),
169        }
170    }
171
172    fn next_window_event_blocking(
173        event_receiver: &mut flume::Receiver<WindowEvent>,
174    ) -> crate::Result<Option<WindowEvent>> {
175        match event_receiver.recv() {
176            Ok(event) => Ok(Some(event)),
177            Err(_) => Err(Error::InternalWindowMessageSend(
178                "Window channel closed".to_owned(),
179            )),
180        }
181    }
182
183    fn next_window_event_timeout(
184        event_receiver: &mut flume::Receiver<WindowEvent>,
185        wait_for: Duration,
186    ) -> crate::Result<Option<WindowEvent>> {
187        match event_receiver.recv_timeout(wait_for) {
188            Ok(event) => Ok(Some(event)),
189            Err(flume::RecvTimeoutError::Timeout) => Ok(None),
190            Err(_) => Err(Error::InternalWindowMessageSend(
191                "Window channel closed".to_owned(),
192            )),
193        }
194    }
195
196    fn next_window_event<T>(
197        event_receiver: &mut flume::Receiver<WindowEvent>,
198        window: &OpenWindow<T>,
199    ) -> crate::Result<Option<WindowEvent>>
200    where
201        T: Window,
202    {
203        #[cfg(target_arch = "wasm32")]
204        {
205            // On wasm, the browser is controlling our async runtime, and we don't have a
206            // good way to do a Timeout-style function This shouldn't have any noticable
207            // effects, because the browser will throttle frames for us automatically
208            // We could refactor to trigger redraws separately from winit, in which case we
209            // could properly use the frame update logic
210            Self::next_window_event_non_blocking(event_receiver)
211        }
212
213        #[cfg(not(target_arch = "wasm32"))]
214        {
215            let next_redraw_target = window.next_redraw_target();
216            if !window.can_wait_for_events() {
217                Self::next_window_event_non_blocking(event_receiver)
218            } else if let Some(redraw_at) = next_redraw_target.next_update_instant() {
219                let timeout_target = redraw_at.timeout_target();
220                let remaining_time =
221                    timeout_target.and_then(|t| t.checked_duration_since(Instant::now()));
222                if let Some(remaining_time) = remaining_time {
223                    Self::next_window_event_timeout(event_receiver, remaining_time)
224                } else {
225                    // No remaining time, only process events that have already arrived.
226                    Self::next_window_event_non_blocking(event_receiver)
227                }
228            } else {
229                Self::next_window_event_blocking(event_receiver)
230            }
231        }
232    }
233
234    fn window_loop<T>(
235        id: WindowId,
236        scene_event_sender: flume::Sender<SceneEvent>,
237        event_sender: flume::Sender<WindowEvent>,
238        mut event_receiver: flume::Receiver<WindowEvent>,
239        initial_system_theme: Theme,
240        window: T,
241    ) -> crate::Result<()>
242    where
243        T: Window,
244    {
245        let scene = Scene::new(scene_event_sender, initial_system_theme);
246
247        let target_fps = window.target_fps();
248        let mut window = OpenWindow::new(window, id, event_sender, scene);
249
250        window.initialize()?;
251        loop {
252            while let Some(event) = match Self::next_window_event(&mut event_receiver, &window) {
253                Ok(event) => event,
254                Err(_) => return Ok(()),
255            } {
256                match event {
257                    WindowEvent::WakeUp | WindowEvent::RedrawRequested => {
258                        window.redraw_status.set_needs_redraw();
259                    }
260                    WindowEvent::SystemThemeChanged(system_theme) => {
261                        window.scene_mut().set_system_theme(system_theme);
262                        window.redraw_status.set_needs_redraw();
263                    }
264                    WindowEvent::Resize { size, scale_factor } => {
265                        window.scene_mut().set_size(size.cast_unit());
266                        window.scene_mut().set_dpi_scale(scale_factor);
267                        window.redraw_status.set_needs_redraw();
268                    }
269                    WindowEvent::CloseRequested => {
270                        if Self::request_window_close(id, &mut window)? {
271                            return Ok(());
272                        }
273                    }
274                    WindowEvent::Input(input) => {
275                        if let Event::Keyboard {
276                            key: Some(key),
277                            state,
278                            ..
279                        } = input.event
280                        {
281                            match state {
282                                ElementState::Pressed => {
283                                    window.scene_mut().keys_pressed.insert(key);
284                                }
285                                ElementState::Released => {
286                                    window.scene_mut().keys_pressed.remove(&key);
287                                }
288                            }
289                        }
290
291                        window.process_input(input)?;
292                    }
293                    WindowEvent::ReceiveCharacter(character) => {
294                        window.receive_character(character)?;
295                    }
296                }
297            }
298
299            // Check for Cmd + W or Alt + f4 to close the window.
300            {
301                let modifiers = window.scene().modifiers_pressed();
302                if modifiers.primary_modifier()
303                    && window.scene().keys_pressed.contains(&VirtualKeyCode::W)
304                    && Self::request_window_close(id, &mut window)?
305                {
306                    return Ok(());
307                }
308            }
309
310            if window.scene().size().area().get() > 0.0 {
311                let additional_scale = window.additional_scale();
312                if additional_scale != window.scene().scale().additional_scale() {
313                    window.scene_mut().set_additional_scale(additional_scale);
314                    WindowMessage::SetAdditionalScale(additional_scale).send_to(id)?;
315                }
316
317                window.scene_mut().start_frame();
318
319                window.update(target_fps)?;
320
321                if window.should_redraw_now() {
322                    window.render()?;
323                    window.scene_mut().end_frame();
324                }
325            }
326        }
327    }
328
329    fn window_main<T>(
330        id: WindowId,
331        scene_event_sender: flume::Sender<SceneEvent>,
332        event_sender: flume::Sender<WindowEvent>,
333        event_receiver: flume::Receiver<WindowEvent>,
334        initial_system_theme: Theme,
335        window: T,
336    ) where
337        T: Window,
338    {
339        Self::window_loop(
340            id,
341            scene_event_sender,
342            event_sender,
343            event_receiver,
344            initial_system_theme,
345            window,
346        )
347        .expect("Error running window loop.");
348    }
349
350    pub(crate) fn count() -> usize {
351        if opened_first_window() {
352            let channels = WINDOW_CHANNELS.lock().unwrap();
353            channels.len()
354        } else {
355            // If our first window hasn't opened, return a count of 1. This will happen in a
356            // single-threaded environment because RuntimeWindow::open is spawned, not
357            // blocked_on.
358            1
359        }
360    }
361
362    pub(crate) fn receive_messages(&mut self) {
363        while let Ok(request) = self.receiver.try_recv() {
364            match request {
365                WindowMessage::RequestClose => {
366                    let _: Result<_, _> = self.event_sender.send(WindowEvent::CloseRequested);
367                }
368                WindowMessage::Close => {
369                    let mut channels = WINDOW_CHANNELS.lock().unwrap();
370                    channels.remove(&self.window_id);
371                    self.keep_running.store(false, Ordering::SeqCst);
372                }
373                WindowMessage::SetAdditionalScale(scale) => {
374                    self.last_known_scale_factor.set_additional_scale(scale);
375                }
376            }
377        }
378    }
379
380    pub(crate) fn request_redraw(&self) {
381        self.event_sender
382            .try_send(WindowEvent::RedrawRequested)
383            .unwrap_or_default();
384    }
385
386    #[instrument(name = "RuntimeWindow::process_event", level = "trace", skip(self))]
387    pub(crate) fn process_event(&mut self, event: &WinitWindowEvent<'_>) {
388        match event {
389            WinitWindowEvent::CloseRequested => {
390                self.event_sender
391                    .try_send(WindowEvent::CloseRequested)
392                    .unwrap_or_default();
393            }
394            WinitWindowEvent::Resized(size) => {
395                self.last_known_size = Size::new(size.width, size.height);
396                self.notify_size_changed();
397            }
398            WinitWindowEvent::ScaleFactorChanged {
399                scale_factor,
400                new_inner_size,
401            } => {
402                self.last_known_scale_factor
403                    .set_dpi_scale(Scale::new(*scale_factor as f32));
404                self.last_known_size = Size::new(new_inner_size.width, new_inner_size.height);
405                self.notify_size_changed();
406            }
407            WinitWindowEvent::KeyboardInput {
408                device_id, input, ..
409            } => self
410                .event_sender
411                .try_send(WindowEvent::Input(InputEvent {
412                    device_id: *device_id,
413                    event: Event::Keyboard {
414                        key: input.virtual_keycode,
415                        state: input.state,
416                        scancode: input.scancode,
417                    },
418                }))
419                .unwrap_or_default(),
420            WinitWindowEvent::MouseInput {
421                device_id,
422                button,
423                state,
424                ..
425            } => self
426                .event_sender
427                .try_send(WindowEvent::Input(InputEvent {
428                    device_id: *device_id,
429                    event: Event::MouseButton {
430                        button: *button,
431                        state: *state,
432                    },
433                }))
434                .unwrap_or_default(),
435            WinitWindowEvent::MouseWheel {
436                device_id,
437                delta,
438                phase,
439                ..
440            } => self
441                .event_sender
442                .try_send(WindowEvent::Input(InputEvent {
443                    device_id: *device_id,
444                    event: Event::MouseWheel {
445                        delta: *delta,
446                        touch_phase: *phase,
447                    },
448                }))
449                .unwrap_or_default(),
450            WinitWindowEvent::CursorMoved {
451                device_id,
452                position,
453                ..
454            } => self
455                .event_sender
456                .try_send(WindowEvent::Input(InputEvent {
457                    device_id: *device_id,
458                    event: Event::MouseMoved {
459                        position: Some(
460                            Point::<f32, Pixels>::new(position.x as f32, position.y as f32)
461                                .to_scaled(&self.last_known_scale_factor),
462                        ),
463                    },
464                }))
465                .unwrap_or_default(),
466            WinitWindowEvent::CursorLeft { device_id } => self
467                .event_sender
468                .try_send(WindowEvent::Input(InputEvent {
469                    device_id: *device_id,
470                    event: Event::MouseMoved { position: None },
471                }))
472                .unwrap_or_default(),
473            WinitWindowEvent::ReceivedCharacter(character) => self
474                .event_sender
475                .try_send(WindowEvent::ReceiveCharacter(*character))
476                .unwrap_or_default(),
477            WinitWindowEvent::ThemeChanged(theme) => self
478                .event_sender
479                .try_send(WindowEvent::SystemThemeChanged(*theme))
480                .unwrap_or_default(),
481            _ => {}
482        }
483    }
484
485    fn notify_size_changed(&mut self) {
486        self.event_sender
487            .try_send(WindowEvent::Resize {
488                size: self.last_known_size,
489                scale_factor: self.last_known_scale_factor.dpi_scale(),
490            })
491            .unwrap_or_default();
492    }
493}
494
495/// A handle to an open window.
496#[derive(Clone, Copy, Debug)]
497pub struct WindowHandle(pub WindowId);
498
499impl WindowHandle {
500    /// Sets the window title.
501    pub fn set_title(&self, title: &str) {
502        if let Some(window) = Runtime::winit_window(self.0) {
503            window.set_title(title);
504        }
505    }
506
507    /// Returns the size of the content area of the window.
508    #[must_use]
509    pub fn inner_size(&self) -> Size<u32, Pixels> {
510        Runtime::winit_window(self.0)
511            .map(|window| Size::new(window.inner_size().width, window.inner_size().height))
512            .unwrap_or_default()
513    }
514
515    /// Attempts to resize the window to `new_size`. This may not work on all platforms.
516    pub fn set_inner_size(&self, new_size: Size<u32, Pixels>) {
517        if let Some(window) = Runtime::winit_window(self.0) {
518            window.set_inner_size(PhysicalSize::new(new_size.width, new_size.height));
519        }
520    }
521
522    /// Returns the position on the screen of the window's top-left corner. On
523    /// platforms where this is unsupported, `innner_position()` is returned.
524    #[must_use]
525    pub fn outer_position(&self) -> Point<i32, Pixels> {
526        Runtime::winit_window(self.0)
527            .and_then(|window| window.outer_position().ok())
528            .map(|position| Point::new(position.x, position.y))
529            .unwrap_or_default()
530    }
531
532    /// Sets the outer position of the window. This may not work on all platforms.
533    pub fn set_outer_position(&self, new_position: Point<i32, Pixels>) {
534        if let Some(window) = Runtime::winit_window(self.0) {
535            window.set_outer_position(PhysicalPosition::new(new_position.x, new_position.y));
536        }
537    }
538
539    /// Returns the position of the top-left of the content area in screen coordinates.
540    #[must_use]
541    pub fn inner_position(&self) -> Point<i32, Pixels> {
542        Runtime::winit_window(self.0)
543            .and_then(|window| window.inner_position().ok())
544            .map(|position| Point::new(position.x, position.y))
545            .unwrap_or_default()
546    }
547
548    /// Sets the window level.
549    pub fn set_window_level(&self, level: WindowLevel) {
550        if let Some(window) = Runtime::winit_window(self.0) {
551            window.set_window_level(level);
552        }
553    }
554
555    /// Returns true if the window is maximized.
556    #[must_use]
557    pub fn maximized(&self) -> bool {
558        if let Some(window) = Runtime::winit_window(self.0) {
559            window.is_maximized()
560        } else {
561            false
562        }
563    }
564
565    /// Sets whether the window should be maximized.
566    pub fn set_maximized(&self, maximized: bool) {
567        if let Some(window) = Runtime::winit_window(self.0) {
568            window.set_maximized(maximized);
569        }
570    }
571
572    /// Sets whether the window should be minimized.
573    pub fn set_minimized(&self, minimized: bool) {
574        if let Some(window) = Runtime::winit_window(self.0) {
575            window.set_minimized(minimized);
576        }
577    }
578
579    /// Requests that the window close.
580    pub fn request_close(&self) {
581        drop(WindowMessage::RequestClose.send_to(self.0));
582    }
583}