use crate::dpi::PhysicalSize;
use std::sync::Arc;
use tracing::debug;
use winit::application::ApplicationHandler;
use winit::dpi;
use winit::dpi::PhysicalPosition;
use winit::error::EventLoopError;
use winit::event::{
DeviceEvent, DeviceId, ElementState, InnerSizeWriter, MouseButton, MouseScrollDelta, Touch,
TouchPhase, WindowEvent,
};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::PhysicalKey;
use winit::window::{Fullscreen, Window, WindowAttributes, WindowId, WindowLevel};
#[cfg(target_arch = "wasm32")]
use tracing::trace;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowAttributesExtWebSys;
#[cfg(target_arch = "wasm32")]
use web_sys::window;
#[cfg(target_arch = "wasm32")]
use web_sys::wasm_bindgen::JsCast;
use winit::window::Fullscreen::Borderless;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WindowMode {
WindowedFullscreen,
Windowed,
WindowedAlwaysOnTop,
}
pub trait AppHandler {
fn min_size(&self) -> (u16, u16);
fn window_mode(&self) -> WindowMode;
fn start_size(&self) -> (u16, u16);
fn cursor_should_be_visible(&self) -> bool;
fn redraw(&mut self) -> bool;
fn got_focus(&mut self);
fn lost_focus(&mut self);
fn window_created(&mut self, window: Arc<Window>);
fn resized(&mut self, size: PhysicalSize<u32>);
fn keyboard_input(&mut self, element_state: ElementState, physical_key: PhysicalKey);
fn cursor_entered(&mut self);
fn cursor_left(&mut self);
fn cursor_moved(&mut self, physical_position: PhysicalPosition<u32>);
fn mouse_input(&mut self, element_state: ElementState, button: MouseButton);
fn mouse_wheel(&mut self, delta: MouseScrollDelta, touch_phase: TouchPhase);
fn pinch_gesture(&mut self, delta: f64, touch_phase: TouchPhase);
fn mouse_motion(&mut self, delta: (f64, f64));
fn touch(&mut self, touch: Touch);
fn scale_factor_changed(&mut self, scale_factor: f64, inner_size_writer: InnerSizeWriter);
}
struct App<'a> {
window: Option<Arc<Window>>,
handler: &'a mut dyn AppHandler,
is_focused: bool,
cursor_is_visible: bool,
title: String,
min_physical_size: PhysicalSize<u32>,
start_physical_size: PhysicalSize<u32>,
mode: WindowMode,
last_set_inner_size: PhysicalSize<u32>,
}
impl<'a> App<'a> {
pub fn new(
handler: &'a mut dyn AppHandler,
title: &str,
min_size: (u16, u16),
start_size: (u16, u16),
mode: WindowMode,
) -> Self {
let min_physical_size = PhysicalSize::new(u32::from(min_size.0), u32::from(min_size.1));
let start_physical_size =
PhysicalSize::new(u32::from(start_size.0), u32::from(start_size.1));
Self {
handler,
window: None,
is_focused: false,
cursor_is_visible: true,
mode,
title: title.to_string(),
min_physical_size,
start_physical_size,
last_set_inner_size: start_physical_size,
}
}
pub fn set_mode(&mut self, mode: &WindowMode) {
let window_ref = self.window.as_ref().unwrap();
match mode {
WindowMode::WindowedFullscreen => {
window_ref.set_window_level(WindowLevel::Normal);
window_ref.set_fullscreen(Some(Borderless(None)));
}
WindowMode::Windowed => {
window_ref.set_window_level(WindowLevel::Normal);
window_ref.set_fullscreen(None);
}
WindowMode::WindowedAlwaysOnTop => {
window_ref.set_fullscreen(None);
window_ref.set_window_level(WindowLevel::AlwaysOnTop);
}
}
self.mode = mode.clone();
}
pub fn internal_resized(&mut self, physical_size: PhysicalSize<u32>) {
debug!("internal resized: {:?}", physical_size);
self.handler.resized(physical_size);
}
pub fn create_window(&mut self, active_event_loop: &ActiveEventLoop) {
assert!(self.window.is_none());
debug!("creating new window");
let window_attributes: WindowAttributes;
#[cfg(not(target_arch = "wasm32"))]
{
let mut calculated_window_attributes = WindowAttributes::default()
.with_title(self.title.as_str())
.with_resizable(true)
.with_inner_size(self.start_physical_size)
.with_min_inner_size(self.min_physical_size);
if self.mode == WindowMode::WindowedFullscreen {
calculated_window_attributes = calculated_window_attributes
.with_fullscreen(Some(Fullscreen::Borderless(None)));
}
window_attributes = calculated_window_attributes;
}
#[cfg(target_arch = "wasm32")]
{
let canvas = window()
.unwrap()
.document()
.unwrap()
.get_element_by_id("limnus_canvas")
.expect("should have a 'limnus_canvas' canvas in the html (dom)")
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
{
trace!(
?canvas,
"found canvas {}x{}",
canvas.width(),
canvas.height()
);
window_attributes = WindowAttributes::default().with_canvas(Some(canvas));
}
}
let window = Arc::new(active_event_loop.create_window(window_attributes).unwrap());
self.window = Some(window.clone());
#[cfg(not(target_arch = "wasm32"))]
if matches!(&self.mode, WindowMode::WindowedAlwaysOnTop) {
window.set_window_level(WindowLevel::AlwaysOnTop);
}
self.handler.window_created(window);
}
}
impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_none() {
self.create_window(event_loop);
if let Some(win) = &self.window {
win.request_redraw();
}
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
if let Some(window) = &self.window
&& id != window.id()
{
return;
}
if let WindowEvent::Resized(_physical_size) = event
&& self.window.is_none()
{
self.create_window(event_loop);
}
let Some(window) = self.window.as_mut() else {
return;
};
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Destroyed => {
self.window = None;
}
WindowEvent::Resized(physical_size) => {
window.request_redraw();
self.internal_resized(physical_size);
}
WindowEvent::RedrawRequested => {
window.request_redraw();
let cursor_visible_request = self.handler.cursor_should_be_visible();
if cursor_visible_request != self.cursor_is_visible {
window.set_cursor_visible(cursor_visible_request);
self.cursor_is_visible = cursor_visible_request;
}
let requested_mode = self.handler.window_mode();
if self.mode != requested_mode {
self.set_mode(&requested_mode);
}
if let Some(found_window) = &self.window {
let requested_size_tuple = self.handler.start_size();
let requested_new_size = PhysicalSize::new(
u32::from(requested_size_tuple.0),
u32::from(requested_size_tuple.1),
);
if requested_new_size.width != self.last_set_inner_size.width
|| requested_new_size.height != self.last_set_inner_size.height
{
debug!(?requested_new_size, "new window inner size requested");
let _ = found_window.request_inner_size(requested_new_size);
self.last_set_inner_size = requested_new_size;
}
let wants_to_keep_going = self.handler.redraw();
if !wants_to_keep_going {
event_loop.exit();
}
}
}
WindowEvent::Focused(is_focus) => {
self.is_focused = is_focus;
if is_focus {
self.handler.got_focus();
} else {
self.handler.lost_focus();
}
}
WindowEvent::KeyboardInput { event, .. } => {
if event.repeat {
return;
}
self.handler.keyboard_input(event.state, event.physical_key);
}
WindowEvent::CursorMoved { position, .. } => self.handler.cursor_moved(
dpi::PhysicalPosition::<u32>::new(position.x as u32, position.y as u32),
),
WindowEvent::CursorEntered { .. } => self.handler.cursor_entered(),
WindowEvent::CursorLeft { .. } => self.handler.cursor_left(),
WindowEvent::MouseWheel { delta, phase, .. } => self.handler.mouse_wheel(delta, phase),
WindowEvent::MouseInput { state, button, .. } => {
self.handler.mouse_input(state, button);
}
WindowEvent::Touch(touch_data) => self.handler.touch(touch_data),
WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer,
} =>
{
self.handler
.scale_factor_changed(scale_factor, inner_size_writer);
}
WindowEvent::PinchGesture { delta, phase, .. } => {
let correct_delta = -delta;
self.handler.pinch_gesture(correct_delta, phase);
}
_ => {}
}
}
fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) {
if let DeviceEvent::MouseMotion { delta } = event
&& self.is_focused
{
self.handler.mouse_motion(delta);
}
}
fn suspended(&mut self, _: &ActiveEventLoop) {}
fn exiting(&mut self, _: &ActiveEventLoop) {}
}
pub struct WindowRunner;
impl WindowRunner {
pub fn run_app(handler: &mut dyn AppHandler, title: &str) -> Result<(), EventLoopError> {
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
let min_size = handler.min_size();
let start_size = handler.start_size();
let mode = handler.window_mode();
let mut app = App::new(handler, title, min_size, start_size, mode);
let _ = event_loop.run_app(&mut app);
Ok(())
}
}