Lines
0 %
Functions
Branches
100 %
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use kludgine_core::easygpu::prelude::*;
use kludgine_core::figures::num_traits::One;
use kludgine_core::figures::{DisplayScale, Displayable, Pixels};
use kludgine_core::math::{Point, Scale, Size};
use kludgine_core::scene::{Scene, SceneEvent};
use kludgine_core::winit::dpi::{PhysicalPosition, PhysicalSize};
use kludgine_core::winit::event::WindowEvent as WinitWindowEvent;
use kludgine_core::winit::window::{Theme, WindowId, WindowLevel};
use kludgine_core::winit::{self};
use kludgine_core::{flume, FrameRenderer};
use once_cell::sync::OnceCell;
use tracing::instrument;
use super::OpenWindow;
use crate::runtime::{Runtime, RuntimeRequest, WINDOWS};
use crate::window::event::{ElementState, Event, InputEvent, VirtualKeyCode, WindowEvent};
use crate::window::{CloseResponse, Window, WindowMessage, WINDOW_CHANNELS};
use crate::Error;
pub struct RuntimeWindow {
pub window_id: WindowId,
pub keep_running: Arc<AtomicBool>,
receiver: flume::Receiver<WindowMessage>,
event_sender: flume::Sender<WindowEvent>,
last_known_size: Size<u32, Pixels>,
last_known_scale_factor: DisplayScale<f32>,
}
pub struct RuntimeWindowConfig {
window_id: WindowId,
instance: wgpu::Instance,
surface: wgpu::Surface,
initial_size: Size<u32, Pixels>,
scale_factor: f32,
impl RuntimeWindowConfig {
pub fn new(window: &winit::window::Window) -> Result<Self, wgpu::CreateSurfaceError> {
// TODO in wasm, we need to explicity enable GL, but since wasm isn't possible
// right now, we're just hardcoding primary
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
});
let surface = unsafe { instance.create_surface(window) }?;
Ok(Self {
window_id: window.id(),
instance,
surface,
initial_size: Size::new(window.inner_size().width, window.inner_size().height),
scale_factor: window.scale_factor() as f32,
})
static OPENED_FIRST_WINDOW: OnceCell<()> = OnceCell::new();
pub fn opened_first_window() -> bool {
OPENED_FIRST_WINDOW.get().is_some()
fn set_opened_first_window() {
OPENED_FIRST_WINDOW.get_or_init(|| ());
#[cfg(not(target_arch = "wasm32"))]
type Format = kludgine_core::sprite::Srgb;
#[cfg(target_arch = "wasm32")]
type Format = kludgine_core::sprite::Normal;
impl RuntimeWindow {
pub(crate) fn open<T>(
window_receiver: &flume::Receiver<RuntimeWindowConfig>,
initial_system_theme: Theme,
app_window: T,
) where
T: Window + Sized + 'static,
{
let RuntimeWindowConfig {
window_id,
initial_size,
scale_factor,
} = window_receiver
.recv()
.expect("Error receiving winit::window");
let (message_sender, message_receiver) = flume::unbounded();
let (event_sender, event_receiver) = flume::unbounded();
let keep_running = Arc::new(AtomicBool::new(true));
let task_keep_running = keep_running.clone();
let window_event_sender = event_sender.clone();
let (scene_event_sender, scene_event_receiver) = flume::unbounded();
// Each window has its own thread/task operating its core update/render
// lifecycle.
std::thread::Builder::new()
.name(String::from("kludgine-window-loop"))
.spawn(move || {
Self::window_main(
scene_event_sender,
window_event_sender,
event_receiver,
initial_system_theme,
app_window,
);
.unwrap();
let renderer = Runtime::block_on(async move {
Renderer::for_surface(surface, &instance, 4)
.await
.expect("Error creating renderer for window")
FrameRenderer::<Format>::run(renderer, task_keep_running, scene_event_receiver, || {
RuntimeRequest::WindowClosed.send();
let mut channels = WINDOW_CHANNELS.lock().unwrap();
channels.insert(window_id, message_sender);
let mut runtime_window = Self {
receiver: message_receiver,
last_known_size: initial_size,
keep_running,
event_sender,
last_known_scale_factor: DisplayScale::new(Scale::new(scale_factor), Scale::one()),
};
runtime_window.notify_size_changed();
let mut windows = WINDOWS.write();
windows.insert(window_id, runtime_window);
set_opened_first_window();
fn request_window_close<T>(id: WindowId, window: &mut OpenWindow<T>) -> crate::Result<bool>
where
T: Window,
if let CloseResponse::RemainOpen = window.request_close()? {
return Ok(false);
WindowMessage::Close.send_to(id)?;
Ok(true)
fn next_window_event_non_blocking(
event_receiver: &mut flume::Receiver<WindowEvent>,
) -> crate::Result<Option<WindowEvent>> {
match event_receiver.try_recv() {
Ok(event) => Ok(Some(event)),
Err(flume::TryRecvError::Empty) => Ok(None),
Err(flume::TryRecvError::Disconnected) => Err(Error::InternalWindowMessageSend(
"Window channel closed".to_owned(),
)),
fn next_window_event_blocking(
match event_receiver.recv() {
Err(_) => Err(Error::InternalWindowMessageSend(
fn next_window_event_timeout(
wait_for: Duration,
match event_receiver.recv_timeout(wait_for) {
Err(flume::RecvTimeoutError::Timeout) => Ok(None),
fn next_window_event<T>(
window: &OpenWindow<T>,
) -> crate::Result<Option<WindowEvent>>
// On wasm, the browser is controlling our async runtime, and we don't have a
// good way to do a Timeout-style function This shouldn't have any noticable
// effects, because the browser will throttle frames for us automatically
// We could refactor to trigger redraws separately from winit, in which case we
// could properly use the frame update logic
Self::next_window_event_non_blocking(event_receiver)
let next_redraw_target = window.next_redraw_target();
if !window.can_wait_for_events() {
} else if let Some(redraw_at) = next_redraw_target.next_update_instant() {
let timeout_target = redraw_at.timeout_target();
let remaining_time =
timeout_target.and_then(|t| t.checked_duration_since(Instant::now()));
if let Some(remaining_time) = remaining_time {
Self::next_window_event_timeout(event_receiver, remaining_time)
} else {
// No remaining time, only process events that have already arrived.
Self::next_window_event_blocking(event_receiver)
fn window_loop<T>(
id: WindowId,
scene_event_sender: flume::Sender<SceneEvent>,
mut event_receiver: flume::Receiver<WindowEvent>,
window: T,
) -> crate::Result<()>
let scene = Scene::new(scene_event_sender, initial_system_theme);
let target_fps = window.target_fps();
let mut window = OpenWindow::new(window, id, event_sender, scene);
window.initialize()?;
loop {
while let Some(event) = match Self::next_window_event(&mut event_receiver, &window) {
Ok(event) => event,
Err(_) => return Ok(()),
} {
match event {
WindowEvent::WakeUp | WindowEvent::RedrawRequested => {
window.redraw_status.set_needs_redraw();
WindowEvent::SystemThemeChanged(system_theme) => {
window.scene_mut().set_system_theme(system_theme);
WindowEvent::Resize { size, scale_factor } => {
window.scene_mut().set_size(size.cast_unit());
window.scene_mut().set_dpi_scale(scale_factor);
WindowEvent::CloseRequested => {
if Self::request_window_close(id, &mut window)? {
return Ok(());
WindowEvent::Input(input) => {
if let Event::Keyboard {
key: Some(key),
state,
..
} = input.event
match state {
ElementState::Pressed => {
window.scene_mut().keys_pressed.insert(key);
ElementState::Released => {
window.scene_mut().keys_pressed.remove(&key);
window.process_input(input)?;
WindowEvent::ReceiveCharacter(character) => {
window.receive_character(character)?;
// Check for Cmd + W or Alt + f4 to close the window.
let modifiers = window.scene().modifiers_pressed();
if modifiers.primary_modifier()
&& window.scene().keys_pressed.contains(&VirtualKeyCode::W)
&& Self::request_window_close(id, &mut window)?
if window.scene().size().area().get() > 0.0 {
let additional_scale = window.additional_scale();
if additional_scale != window.scene().scale().additional_scale() {
window.scene_mut().set_additional_scale(additional_scale);
WindowMessage::SetAdditionalScale(additional_scale).send_to(id)?;
window.scene_mut().start_frame();
window.update(target_fps)?;
if window.should_redraw_now() {
window.render()?;
window.scene_mut().end_frame();
fn window_main<T>(
event_receiver: flume::Receiver<WindowEvent>,
Self::window_loop(
id,
window,
)
.expect("Error running window loop.");
pub(crate) fn count() -> usize {
if opened_first_window() {
let channels = WINDOW_CHANNELS.lock().unwrap();
channels.len()
// If our first window hasn't opened, return a count of 1. This will happen in a
// single-threaded environment because RuntimeWindow::open is spawned, not
// blocked_on.
1
pub(crate) fn receive_messages(&mut self) {
while let Ok(request) = self.receiver.try_recv() {
match request {
WindowMessage::RequestClose => {
let _: Result<_, _> = self.event_sender.send(WindowEvent::CloseRequested);
WindowMessage::Close => {
channels.remove(&self.window_id);
self.keep_running.store(false, Ordering::SeqCst);
WindowMessage::SetAdditionalScale(scale) => {
self.last_known_scale_factor.set_additional_scale(scale);
pub(crate) fn request_redraw(&self) {
self.event_sender
.try_send(WindowEvent::RedrawRequested)
.unwrap_or_default();
#[instrument(name = "RuntimeWindow::process_event", level = "trace", skip(self))]
pub(crate) fn process_event(&mut self, event: &WinitWindowEvent<'_>) {
WinitWindowEvent::CloseRequested => {
.try_send(WindowEvent::CloseRequested)
WinitWindowEvent::Resized(size) => {
self.last_known_size = Size::new(size.width, size.height);
self.notify_size_changed();
WinitWindowEvent::ScaleFactorChanged {
new_inner_size,
} => {
self.last_known_scale_factor
.set_dpi_scale(Scale::new(*scale_factor as f32));
self.last_known_size = Size::new(new_inner_size.width, new_inner_size.height);
WinitWindowEvent::KeyboardInput {
device_id, input, ..
} => self
.event_sender
.try_send(WindowEvent::Input(InputEvent {
device_id: *device_id,
event: Event::Keyboard {
key: input.virtual_keycode,
state: input.state,
scancode: input.scancode,
},
}))
.unwrap_or_default(),
WinitWindowEvent::MouseInput {
device_id,
button,
event: Event::MouseButton {
button: *button,
state: *state,
WinitWindowEvent::MouseWheel {
delta,
phase,
event: Event::MouseWheel {
delta: *delta,
touch_phase: *phase,
WinitWindowEvent::CursorMoved {
position,
event: Event::MouseMoved {
position: Some(
Point::<f32, Pixels>::new(position.x as f32, position.y as f32)
.to_scaled(&self.last_known_scale_factor),
),
WinitWindowEvent::CursorLeft { device_id } => self
event: Event::MouseMoved { position: None },
WinitWindowEvent::ReceivedCharacter(character) => self
.try_send(WindowEvent::ReceiveCharacter(*character))
WinitWindowEvent::ThemeChanged(theme) => self
.try_send(WindowEvent::SystemThemeChanged(*theme))
_ => {}
fn notify_size_changed(&mut self) {
.try_send(WindowEvent::Resize {
size: self.last_known_size,
scale_factor: self.last_known_scale_factor.dpi_scale(),
/// A handle to an open window.
#[derive(Clone, Copy, Debug)]
pub struct WindowHandle(pub WindowId);
impl WindowHandle {
/// Sets the window title.
pub fn set_title(&self, title: &str) {
if let Some(window) = Runtime::winit_window(self.0) {
window.set_title(title);
/// Returns the size of the content area of the window.
#[must_use]
pub fn inner_size(&self) -> Size<u32, Pixels> {
Runtime::winit_window(self.0)
.map(|window| Size::new(window.inner_size().width, window.inner_size().height))
.unwrap_or_default()
/// Attempts to resize the window to `new_size`. This may not work on all platforms.
pub fn set_inner_size(&self, new_size: Size<u32, Pixels>) {
window.set_inner_size(PhysicalSize::new(new_size.width, new_size.height));
/// Returns the position on the screen of the window's top-left corner. On
/// platforms where this is unsupported, `innner_position()` is returned.
pub fn outer_position(&self) -> Point<i32, Pixels> {
.and_then(|window| window.outer_position().ok())
.map(|position| Point::new(position.x, position.y))
/// Sets the outer position of the window. This may not work on all platforms.
pub fn set_outer_position(&self, new_position: Point<i32, Pixels>) {
window.set_outer_position(PhysicalPosition::new(new_position.x, new_position.y));
/// Returns the position of the top-left of the content area in screen coordinates.
pub fn inner_position(&self) -> Point<i32, Pixels> {
.and_then(|window| window.inner_position().ok())
/// Sets the window level.
pub fn set_window_level(&self, level: WindowLevel) {
window.set_window_level(level);
/// Returns true if the window is maximized.
pub fn maximized(&self) -> bool {
window.is_maximized()
false
/// Sets whether the window should be maximized.
pub fn set_maximized(&self, maximized: bool) {
window.set_maximized(maximized);
/// Sets whether the window should be minimized.
pub fn set_minimized(&self, minimized: bool) {
window.set_minimized(minimized);
/// Requests that the window close.
pub fn request_close(&self) {
drop(WindowMessage::RequestClose.send_to(self.0));