use winit::{self, dpi};
pub use winit::event::{MouseButton, ScanCode};
#[cfg(feature = "gamepad")]
pub use gilrs::Axis;
#[cfg(feature = "gamepad")]
pub use gilrs::Button;
pub mod winit_event {
pub use super::winit::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta,
TouchPhase, WindowEvent,
};
}
#[cfg(feature = "gamepad")]
pub use crate::input::gamepad::GamepadId;
use crate::input::keyboard::{KeyCode, KeyInput, KeyMods};
use crate::GameError;
use self::winit_event::{
ElementState, Event, KeyboardInput, MouseScrollDelta, TouchPhase, WindowEvent,
};
pub use winit::event_loop::{ControlFlow, EventLoop};
use crate::context::Context;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ErrorOrigin {
Update,
Draw,
MouseButtonDownEvent,
MouseButtonUpEvent,
MouseMotionEvent,
MouseEnterOrLeave,
MouseWheelEvent,
KeyDownEvent,
KeyUpEvent,
TextInputEvent,
TouchEvent,
GamepadButtonDownEvent,
GamepadButtonUpEvent,
GamepadAxisEvent,
FocusEvent,
QuitEvent,
ResizeEvent,
}
pub trait EventHandler<E = GameError>
where
E: std::fmt::Debug,
{
fn update(&mut self, _ctx: &mut Context) -> Result<(), E>;
fn draw(&mut self, _ctx: &mut Context) -> Result<(), E>;
fn mouse_button_down_event(
&mut self,
_ctx: &mut Context,
_button: MouseButton,
_x: f32,
_y: f32,
) -> Result<(), E> {
Ok(())
}
fn mouse_button_up_event(
&mut self,
_ctx: &mut Context,
_button: MouseButton,
_x: f32,
_y: f32,
) -> Result<(), E> {
Ok(())
}
fn mouse_motion_event(
&mut self,
_ctx: &mut Context,
_x: f32,
_y: f32,
_dx: f32,
_dy: f32,
) -> Result<(), E> {
Ok(())
}
fn mouse_enter_or_leave(&mut self, _ctx: &mut Context, _entered: bool) -> Result<(), E> {
Ok(())
}
fn mouse_wheel_event(&mut self, _ctx: &mut Context, _x: f32, _y: f32) -> Result<(), E> {
Ok(())
}
fn key_down_event(
&mut self,
ctx: &mut Context,
input: KeyInput,
_repeated: bool,
) -> Result<(), E> {
if input.keycode == Some(KeyCode::Escape) {
ctx.request_quit();
}
Ok(())
}
fn key_up_event(&mut self, _ctx: &mut Context, _input: KeyInput) -> Result<(), E> {
Ok(())
}
fn text_input_event(&mut self, _ctx: &mut Context, _character: char) -> Result<(), E> {
Ok(())
}
fn touch_event(
&mut self,
ctx: &mut Context,
phase: TouchPhase,
x: f64,
y: f64,
) -> Result<(), E> {
ctx.mouse.handle_move(x as f32, y as f32);
match phase {
TouchPhase::Started => {
ctx.mouse.set_button(MouseButton::Left, true);
self.mouse_button_down_event(ctx, MouseButton::Left, x as f32, y as f32)?;
}
TouchPhase::Moved => {
let diff = ctx.mouse.last_delta();
self.mouse_motion_event(ctx, x as f32, y as f32, diff.x, diff.y)?;
}
TouchPhase::Ended | TouchPhase::Cancelled => {
ctx.mouse.set_button(MouseButton::Left, false);
self.mouse_button_up_event(ctx, MouseButton::Left, x as f32, y as f32)?;
}
}
Ok(())
}
#[cfg(feature = "gamepad")]
fn gamepad_button_down_event(
&mut self,
_ctx: &mut Context,
_btn: gilrs::Button,
_id: GamepadId,
) -> Result<(), E> {
Ok(())
}
#[cfg(feature = "gamepad")]
fn gamepad_button_up_event(
&mut self,
_ctx: &mut Context,
_btn: gilrs::Button,
_id: GamepadId,
) -> Result<(), E> {
Ok(())
}
#[cfg(feature = "gamepad")]
fn gamepad_axis_event(
&mut self,
_ctx: &mut Context,
_axis: gilrs::Axis,
_value: f32,
_id: GamepadId,
) -> Result<(), E> {
Ok(())
}
fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) -> Result<(), E> {
Ok(())
}
fn quit_event(&mut self, _ctx: &mut Context) -> Result<bool, E> {
debug!("quit_event() callback called, quitting...");
Ok(false)
}
fn resize_event(&mut self, _ctx: &mut Context, _width: f32, _height: f32) -> Result<(), E> {
Ok(())
}
fn on_error(&mut self, _ctx: &mut Context, _origin: ErrorOrigin, _e: E) -> bool {
true
}
}
#[allow(clippy::needless_return)] pub fn run<S: 'static, E>(mut ctx: Context, event_loop: EventLoop<()>, mut state: S) -> !
where
S: EventHandler<E>,
E: std::fmt::Debug,
{
event_loop.run(move |mut event, _, control_flow| {
let ctx = &mut ctx;
let state = &mut state;
if ctx.quit_requested {
let res = state.quit_event(ctx);
ctx.quit_requested = false;
if let Ok(false) = res {
ctx.continuing = false;
} else if catch_error(ctx, res, state, control_flow, ErrorOrigin::QuitEvent) {
return;
}
}
if !ctx.continuing {
*control_flow = ControlFlow::Exit;
return;
}
*control_flow = ControlFlow::Poll;
process_event(ctx, &mut event);
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(logical_size) => {
let res = state.resize_event(
ctx,
logical_size.width as f32,
logical_size.height as f32,
);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::ResizeEvent) {
return;
};
}
WindowEvent::CloseRequested => {
let res = state.quit_event(ctx);
if let Ok(false) = res {
ctx.continuing = false;
} else if catch_error(ctx, res, state, control_flow, ErrorOrigin::QuitEvent) {
return;
}
}
WindowEvent::Focused(gained) => {
let res = state.focus_event(ctx, gained);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::FocusEvent) {
return;
};
}
WindowEvent::ReceivedCharacter(ch) => {
let res = state.text_input_event(ctx, ch);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::TextInputEvent) {
return;
};
}
WindowEvent::ModifiersChanged(mods) => {
ctx.keyboard.set_modifiers(KeyMods::from(mods))
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: keycode,
scancode,
..
},
..
} => {
let repeat = ctx.keyboard.is_key_repeated();
let res = state.key_down_event(
ctx,
KeyInput {
scancode,
keycode,
mods: ctx.keyboard.active_mods(),
},
repeat,
);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::KeyDownEvent) {
return;
};
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: keycode,
scancode,
..
},
..
} => {
let res = state.key_up_event(
ctx,
KeyInput {
scancode,
keycode,
mods: ctx.keyboard.active_mods(),
},
);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::KeyUpEvent) {
return;
};
}
WindowEvent::MouseWheel { delta, .. } => {
let (x, y) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x, y),
MouseScrollDelta::PixelDelta(pos) => {
let scale_factor = ctx.gfx.window.scale_factor();
let dpi::LogicalPosition { x, y } = pos.to_logical::<f32>(scale_factor);
(x, y)
}
};
let res = state.mouse_wheel_event(ctx, x, y);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::MouseWheelEvent) {
return;
};
}
WindowEvent::MouseInput {
state: element_state,
button,
..
} => {
let position = ctx.mouse.position();
match element_state {
ElementState::Pressed => {
let res =
state.mouse_button_down_event(ctx, button, position.x, position.y);
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::MouseButtonDownEvent,
) {
return;
};
}
ElementState::Released => {
let res =
state.mouse_button_up_event(ctx, button, position.x, position.y);
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::MouseButtonUpEvent,
) {
return;
};
}
}
}
WindowEvent::CursorMoved { .. } => {
let position = ctx.mouse.position();
let delta = ctx.mouse.last_delta();
let res =
state.mouse_motion_event(ctx, position.x, position.y, delta.x, delta.y);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::MouseMotionEvent) {
return;
};
}
WindowEvent::Touch(touch) => {
let res =
state.touch_event(ctx, touch.phase, touch.location.x, touch.location.y);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::TouchEvent) {
return;
};
}
WindowEvent::CursorEntered { device_id: _ } => {
let res = state.mouse_enter_or_leave(ctx, true);
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::MouseEnterOrLeave,
) {
return;
}
}
WindowEvent::CursorLeft { device_id: _ } => {
let res = state.mouse_enter_or_leave(ctx, false);
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::MouseEnterOrLeave,
) {
return;
}
}
_x => {
}
},
Event::DeviceEvent { .. } => (),
Event::Resumed => (),
Event::Suspended => (),
Event::NewEvents(_) => (),
Event::UserEvent(_) => (),
Event::MainEventsCleared => {
ctx.time.tick();
#[cfg(feature = "gamepad")]
while let Some(gilrs::Event { id, event, .. }) = ctx.gamepad.next_event() {
match event {
gilrs::EventType::ButtonPressed(button, _) => {
let res = state.gamepad_button_down_event(ctx, button, GamepadId(id));
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::GamepadButtonDownEvent,
) {
return;
};
}
gilrs::EventType::ButtonReleased(button, _) => {
let res = state.gamepad_button_up_event(ctx, button, GamepadId(id));
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::GamepadButtonUpEvent,
) {
return;
};
}
gilrs::EventType::AxisChanged(axis, value, _) => {
let res = state.gamepad_axis_event(ctx, axis, value, GamepadId(id));
if catch_error(
ctx,
res,
state,
control_flow,
ErrorOrigin::GamepadAxisEvent,
) {
return;
};
}
_ => {}
}
}
let res = state.update(ctx);
if catch_error(ctx, res, state, control_flow, ErrorOrigin::Update) {
return;
};
if let Err(e) = ctx.gfx.begin_frame() {
error!("Error on GraphicsContext::begin_frame(): {e:?}");
eprintln!("Error on GraphicsContext::begin_frame(): {e:?}");
*control_flow = ControlFlow::Exit;
}
if let Err(e) = state.draw(ctx) {
error!("Error on EventHandler::draw(): {e:?}");
eprintln!("Error on EventHandler::draw(): {e:?}");
if state.on_error(ctx, ErrorOrigin::Draw, e) {
*control_flow = ControlFlow::Exit;
return;
}
}
if let Err(e) = ctx.gfx.end_frame() {
error!("Error on GraphicsContext::end_frame(): {e:?}");
eprintln!("Error on GraphicsContext::end_frame(): {e:?}");
*control_flow = ControlFlow::Exit;
}
ctx.mouse.reset_delta();
ctx.keyboard.save_keyboard_state();
ctx.mouse.save_mouse_state();
}
Event::RedrawRequested(_) => (),
Event::RedrawEventsCleared => (),
Event::LoopDestroyed => (),
}
})
}
fn catch_error<T, E, S: 'static>(
ctx: &mut Context,
event_result: Result<T, E>,
state: &mut S,
control_flow: &mut ControlFlow,
origin: ErrorOrigin,
) -> bool
where
S: EventHandler<E>,
E: std::fmt::Debug,
{
if let Err(e) = event_result {
error!("Error on EventHandler {origin:?}: {e:?}");
eprintln!("Error on EventHandler {origin:?}: {e:?}");
if state.on_error(ctx, origin, e) {
*control_flow = ControlFlow::Exit;
return true;
}
}
false
}
pub fn process_event(ctx: &mut Context, event: &mut winit::event::Event<()>) {
if let winit_event::Event::WindowEvent { event, .. } = event {
match event {
winit_event::WindowEvent::Resized(physical_size) => {
ctx.gfx.resize(*physical_size);
}
winit_event::WindowEvent::CursorMoved {
position: physical_position,
..
} => {
ctx.mouse
.handle_move(physical_position.x as f32, physical_position.y as f32);
}
winit_event::WindowEvent::MouseInput { button, state, .. } => {
let pressed = match state {
winit_event::ElementState::Pressed => true,
winit_event::ElementState::Released => false,
};
ctx.mouse.set_button(*button, pressed);
}
winit_event::WindowEvent::ModifiersChanged(mods) => {
ctx.keyboard.set_modifiers(KeyMods::from(*mods))
}
winit_event::WindowEvent::KeyboardInput {
input:
winit::event::KeyboardInput {
state,
scancode,
virtual_keycode: keycode,
..
},
..
} => {
let pressed = match state {
winit_event::ElementState::Pressed => true,
winit_event::ElementState::Released => false,
};
ctx.keyboard.set_scancode(*scancode, pressed);
if let Some(key) = keycode {
ctx.keyboard.set_key(*key, pressed);
}
}
winit_event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
if !ctx.conf.window_mode.resize_on_scale_factor_change {
**new_inner_size = winit::dpi::PhysicalSize::<u32>::from([
ctx.conf.window_mode.width,
ctx.conf.window_mode.height,
]);
}
}
_ => (),
}
};
}