use std::{
path::Path,
time::{Duration, Instant},
};
use image::ImageError;
use wgpu::TextureViewDescriptor;
use winit::{
application::ApplicationHandler,
event::{DeviceEvent, DeviceId, WindowEvent},
event_loop::ActiveEventLoop,
window::{Icon, WindowAttributes, WindowId},
};
use crate::{
EngineUpdates, Scene, UiLayoutSides, UiLayoutTopBottom, UiSettings,
system::{State, process_engine_updates},
};
fn load_icon(path: &Path) -> Result<Icon, ImageError> {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)?.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
Ok(Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon"))
}
impl<T, FRender, FEventDev, FEventWin, FGui> State<T, FRender, FEventDev, FEventWin, FGui>
where
FRender: FnMut(&mut T, &mut Scene, f32) -> EngineUpdates + 'static,
FEventDev: FnMut(&mut T, DeviceEvent, &mut Scene, bool, f32) -> EngineUpdates + 'static,
FEventWin: FnMut(&mut T, WindowEvent, &mut Scene, f32) -> EngineUpdates + 'static,
FGui: FnMut(&mut T, &egui::Context, &mut Scene) -> EngineUpdates + 'static,
{
fn redraw(&mut self) {
if self.paused || self.render.is_none() || self.graphics.is_none() {
return;
}
let sys = self.render.as_ref().unwrap();
let graphics = self.graphics.as_mut().unwrap();
let now = Instant::now();
self.dt = now - self.last_render_time;
if self.dt.as_secs() > 1 {
self.dt = Duration::from_secs(1);
}
self.last_render_time = now;
let dt_secs = self.dt.as_secs() as f32 + self.dt.subsec_micros() as f32 / 1_000_000.;
let updates_render =
(self.render_handler)(&mut self.user_state, &mut graphics.scene, dt_secs);
process_engine_updates(
&updates_render,
graphics,
&self.render.as_ref().unwrap().device,
&self.render.as_ref().unwrap().queue,
);
match sys.surface.get_current_texture() {
Ok(output_frame) => {
let surface_texture = output_frame
.texture
.create_view(&TextureViewDescriptor::default());
let resize_required = graphics.render(
self.gui.as_mut().unwrap(),
output_frame,
&surface_texture,
&sys.device,
&sys.queue,
self.dt,
sys.surface_cfg.width,
sys.surface_cfg.height,
&mut self.ui_settings,
&mut self.gui_handler,
&mut self.user_state,
);
if resize_required {
self.resize(sys.size);
}
let pending = self.graphics.as_mut().unwrap().pending_msaa.take();
if let Some(new_msaa) = pending {
let device = &self.render.as_ref().unwrap().device;
let graphics = self.graphics.as_mut().unwrap();
graphics.msaa_samples = new_msaa;
graphics.apply_msaa_change(device);
self.graphics_settings.msaa_samples = new_msaa;
}
}
Err(_e) => (),
}
}
}
impl<T, FRender, FEventDev, FEventWin, FGui> ApplicationHandler
for State<T, FRender, FEventDev, FEventWin, FGui>
where
FRender: FnMut(&mut T, &mut Scene, f32) -> EngineUpdates + 'static,
FEventDev: FnMut(&mut T, DeviceEvent, &mut Scene, bool, f32) -> EngineUpdates + 'static,
FEventWin: FnMut(&mut T, WindowEvent, &mut Scene, f32) -> EngineUpdates + 'static,
FGui: FnMut(&mut T, &egui::Context, &mut Scene) -> EngineUpdates + 'static,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let icon = match self.ui_settings.icon_path {
Some(ref p) => load_icon(Path::new(&p)).ok(),
None => None,
};
let requested_w = self.scene.window_size.0;
let requested_h = self.scene.window_size.1;
let fits_on_screen = event_loop
.primary_monitor()
.map(|m| {
let scale = m.scale_factor() as f32;
let logical_w = m.size().width as f32 / scale;
let logical_h = m.size().height as f32 / scale;
requested_w <= logical_w && requested_h <= logical_h
})
.unwrap_or(true);
let base_attributes = WindowAttributes::default()
.with_title(&self.scene.window_title)
.with_window_icon(icon);
let attributes = if fits_on_screen {
base_attributes.with_inner_size(winit::dpi::LogicalSize::new(requested_w, requested_h))
} else {
base_attributes.with_maximized(true)
};
let window = event_loop.create_window(attributes).unwrap();
self.init(window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
if self.render.is_none() || self.graphics.is_none() {
return;
}
let graphics = &mut self.graphics.as_mut().unwrap();
let gui = &mut self.gui.as_mut().unwrap();
if !gui.mouse_in_gui {
graphics.handle_input_window(&event, &self.scene.input_settings);
let _inputs_present = graphics.inputs_commanded.inputs_present();
let dt_secs = self.dt.as_secs() as f32 + self.dt.subsec_micros() as f32 / 1_000_000.;
let updates_event = (self.event_win_handler)(
&mut self.user_state,
event.clone(),
&mut graphics.scene,
dt_secs,
);
let render = self.render.as_ref().unwrap();
process_engine_updates(&updates_event, graphics, &render.device, &render.queue);
}
let window = &graphics.window;
let _ = gui.egui_state.on_window_event(window, &event);
match event {
WindowEvent::RedrawRequested => {
self.redraw();
self.graphics.as_ref().unwrap().window.request_redraw();
}
WindowEvent::CursorMoved { position, .. } => {
let in_ui_horizontal = match self.ui_settings.layout_sides {
UiLayoutSides::Left => position.x < gui.size.0 as f64,
UiLayoutSides::Right => {
position.x > window.inner_size().width as f64 - gui.size.0 as f64
}
};
let in_ui_vertical = match self.ui_settings.layout_top_bottom {
UiLayoutTopBottom::Top => position.y < gui.size.1 as f64,
UiLayoutTopBottom::Bottom => {
position.y > window.inner_size().height as f64 - gui.size.1 as f64
}
};
let mouse_in_gui = in_ui_horizontal || in_ui_vertical;
if mouse_in_gui {
gui.mouse_in_gui = true;
self.graphics.as_mut().unwrap().inputs_commanded = Default::default();
} else {
gui.mouse_in_gui = false;
}
}
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
self.paused = physical_size.width == 0 || physical_size.height == 0;
if !self.paused {
self.resize(physical_size);
self.last_render_time = Instant::now();
self.dt = Default::default();
}
self.graphics.as_mut().unwrap().inputs_commanded.free_look = false;
}
WindowEvent::ScaleFactorChanged {
scale_factor: _,
inner_size_writer: _,
..
} => {
println!("Scale factor changed");
}
WindowEvent::Moved(_) => {
gui.mouse_in_gui = true;
self.graphics.as_mut().unwrap().inputs_commanded.free_look = false;
}
WindowEvent::Occluded(occ) => {
self.paused = occ;
self.graphics.as_mut().unwrap().inputs_commanded.free_look = false;
if !self.paused {
self.last_render_time = Instant::now();
self.dt = Default::default();
}
}
WindowEvent::Focused(focused) => {
self.paused = !focused;
self.graphics.as_mut().unwrap().inputs_commanded.free_look = false;
if focused {
self.last_render_time = Instant::now();
self.dt = Default::default();
}
}
WindowEvent::CursorLeft { device_id: _ } => {
graphics.inputs_commanded.free_look = false;
graphics.inputs_commanded.cursor_out_of_window = true;
}
WindowEvent::CursorEntered { device_id: _ } => {
self.paused = false;
graphics.inputs_commanded.cursor_out_of_window = false;
}
WindowEvent::HoveredFile(_)
| WindowEvent::HoveredFileCancelled
| WindowEvent::DroppedFile(_) => {
self.graphics.as_ref().unwrap().window.request_redraw();
}
_ => {}
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: DeviceId,
event: DeviceEvent,
) {
if self.render.is_none() || self.graphics.is_none() {
return;
}
let render = &self.render.as_ref().unwrap();
let graphics = &mut self.graphics.as_mut().unwrap();
let gui = &mut self.gui.as_mut().unwrap();
if !gui.mouse_in_gui {
graphics.handle_input_device(&event, &self.scene.input_settings);
let inputs_present = graphics.inputs_commanded.inputs_present();
let dt_secs = self.dt.as_secs() as f32 + self.dt.subsec_micros() as f32 / 1_000_000.;
let updates_event = (self.event_dev_handler)(
&mut self.user_state,
event,
&mut graphics.scene,
inputs_present,
dt_secs,
);
process_engine_updates(&updates_event, graphics, &render.device, &render.queue);
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {}
}
pub fn viewport_rect(
ui_size: (f32, f32), win_width: u32,
win_height: u32,
ui_settings: &UiSettings,
_pixels_per_pt: f32,
) -> (f32, f32, f32, f32) {
let mut x = 0.0;
let mut y = 0.0;
let mut eff_width = win_width as f32;
let mut eff_height = win_height as f32;
if ui_settings.layout_sides == UiLayoutSides::Left {
x = ui_size.0;
}
if ui_settings.layout_top_bottom == UiLayoutTopBottom::Top {
y = ui_size.1;
}
eff_width -= ui_size.0;
eff_height -= ui_size.1;
if eff_width < 1.0 {
eff_width = 1.0;
}
if eff_height < 1.0 {
eff_height = 1.0;
}
(x, y, eff_width, eff_height)
}