use std::sync::{Arc, Mutex};
use winit::{
application::ApplicationHandler,
event::{DeviceEvent, DeviceId, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::{Fullscreen, Window as OsWindow, WindowId},
};
use winit_input_helper::WinitInputHelper;
use crate::{
ecs::plugin::Plugin,
graphics::types::{CursorGrabMode, CursorIcon, KeyCode, MouseButton},
};
pub struct WindowConfig {
pub title: String,
pub width: u32,
pub height: u32,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "Pebble".to_string(),
width: 1280,
height: 720,
}
}
}
#[derive(Clone)]
pub struct Window(Arc<OsWindow>);
impl Window {
fn new(handle: Arc<OsWindow>) -> Self {
Self(handle)
}
pub(crate) fn raw(&self) -> Arc<OsWindow> {
self.0.clone()
}
pub fn set_title(&self, title: &str) {
self.0.set_title(title);
}
pub fn inner_size(&self) -> (u32, u32) {
let size = self.0.inner_size();
(size.width, size.height)
}
pub fn set_inner_size(&self, width: u32, height: u32) {
let _ = self
.0
.request_inner_size(winit::dpi::PhysicalSize::new(width, height));
}
pub fn set_resizable(&self, resizable: bool) {
self.0.set_resizable(resizable);
}
pub fn set_visible(&self, visible: bool) {
self.0.set_visible(visible);
}
pub fn set_minimized(&self, minimized: bool) {
self.0.set_minimized(minimized);
}
pub fn set_maximized(&self, maximized: bool) {
self.0.set_maximized(maximized);
}
pub fn set_decorations(&self, decorations: bool) {
self.0.set_decorations(decorations);
}
pub fn focus(&self) {
self.0.focus_window();
}
pub fn set_fullscreen(&self, fullscreen: bool) {
self.0
.set_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
}
pub fn is_fullscreen(&self) -> bool {
self.0.fullscreen().is_some()
}
pub fn set_cursor_icon(&self, icon: CursorIcon) {
self.0.set_cursor(winit::window::CursorIcon::from(icon));
}
pub fn set_cursor_visible(&self, visible: bool) {
self.0.set_cursor_visible(visible);
}
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> bool {
self.0.set_cursor_grab(mode.into()).is_ok()
}
pub fn request_redraw(&self) {
self.0.request_redraw();
}
}
struct InputState {
helper: WinitInputHelper,
}
#[derive(Clone)]
pub struct Input(Arc<Mutex<InputState>>);
impl Input {
fn new() -> Self {
Self(Arc::new(Mutex::new(InputState {
helper: WinitInputHelper::new(),
})))
}
fn step(&self) {
self.0.lock().unwrap().helper.step();
}
fn process_window_event(&self, event: &WindowEvent) {
self.0.lock().unwrap().helper.process_window_event(event);
}
fn process_device_event(&self, event: &DeviceEvent) {
self.0.lock().unwrap().helper.process_device_event(event);
}
fn end_step(&self) {
self.0.lock().unwrap().helper.end_step();
}
pub fn key_pressed(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().helper.key_pressed(key.into())
}
pub fn key_released(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().helper.key_released(key.into())
}
pub fn key_held(&self, key: KeyCode) -> bool {
self.0.lock().unwrap().helper.key_held(key.into())
}
pub fn mouse_pressed(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().helper.mouse_pressed(button.into())
}
pub fn mouse_released(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().helper.mouse_released(button.into())
}
pub fn mouse_held(&self, button: MouseButton) -> bool {
self.0.lock().unwrap().helper.mouse_held(button.into())
}
pub fn cursor(&self) -> Option<(f32, f32)> {
self.0.lock().unwrap().helper.cursor()
}
pub fn cursor_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().helper.cursor_diff()
}
pub fn mouse_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().helper.mouse_diff()
}
pub fn scroll_diff(&self) -> (f32, f32) {
self.0.lock().unwrap().helper.scroll_diff()
}
pub fn close_requested(&self) -> bool {
self.0.lock().unwrap().helper.close_requested()
}
pub fn resolution(&self) -> Option<(u32, u32)> {
self.0.lock().unwrap().helper.resolution()
}
}
pub struct WindowPlugin {
config: WindowConfig,
}
impl WindowPlugin {
pub fn new(config: WindowConfig) -> Self {
Self { config }
}
}
impl Default for WindowPlugin {
fn default() -> Self {
Self::new(WindowConfig::default())
}
}
struct WinitApp {
config: Option<WindowConfig>,
app: crate::app::App,
input: Input,
window: Option<Arc<OsWindow>>,
pending_window_events: Vec<WindowEvent>,
pending_device_events: Vec<DeviceEvent>,
}
impl ApplicationHandler for WinitApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let Some(config) = self.config.take() else {
return;
};
#[allow(unused_mut)]
let mut attrs = OsWindow::default_attributes()
.with_title(config.title)
.with_inner_size(winit::dpi::PhysicalSize::new(config.width, config.height));
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowAttributesExtWebSys;
attrs = attrs.with_append(true);
}
let os_window = Arc::new(event_loop.create_window(attrs).unwrap());
self.window = Some(os_window.clone());
let window = Window::new(os_window);
self.app = std::mem::take(&mut self.app)
.insert_resource(window)
.insert_resource(self.input.clone());
self.window.as_ref().unwrap().request_redraw();
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::RedrawRequested => {
self.input.step();
for pending in self.pending_window_events.drain(..) {
self.input.process_window_event(&pending);
}
for pending in self.pending_device_events.drain(..) {
self.input.process_device_event(&pending);
}
self.input.end_step();
self.app.update();
if self.app.should_exit() {
event_loop.exit();
return;
}
self.window.as_ref().unwrap().request_redraw();
}
other => self.pending_window_events.push(other),
}
}
fn device_event(&mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, event: DeviceEvent) {
self.pending_device_events.push(event);
}
}
impl Plugin for WindowPlugin {
fn build(self, app: crate::app::App) -> crate::app::App {
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Wait);
app.set_runner(move |app| {
let handler = WinitApp {
config: Some(self.config),
app,
input: Input::new(),
window: None,
pending_window_events: Vec::new(),
pending_device_events: Vec::new(),
};
#[cfg(not(target_arch = "wasm32"))]
{
let mut handler = handler;
event_loop.run_app(&mut handler).unwrap();
}
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn_app(handler);
}
})
}
}