#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
extern crate input;
extern crate window;
extern crate winit;
use std::sync::Arc;
use rustc_hash::FxHashMap;
use input::{
Button, ButtonArgs, ButtonState, CloseArgs, Event, Input, Key, Motion, MouseButton, ResizeArgs,
};
use std::{collections::VecDeque, error::Error, time::Duration};
use window::{AdvancedWindow, BuildFromWindowSettings, Position, Size, Window, WindowSettings};
use winit::{
application::ApplicationHandler,
dpi::{LogicalPosition, LogicalSize},
event::{
DeviceId,
ElementState,
MouseScrollDelta,
WindowEvent,
},
event_loop::{ActiveEventLoop, EventLoop},
window::WindowId,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum KeyboardIgnoreModifiers {
None,
AbcKeyCode,
}
pub struct WinitWindow {
pub event_loop: Option<EventLoop<UserEvent>>,
pub keyboard_ignore_modifiers: KeyboardIgnoreModifiers,
pub window: Option<Arc<winit::window::Window>>,
pub devices: u32,
pub device_id_map: FxHashMap<DeviceId, u32>,
settings: WindowSettings,
title: String,
exit_on_esc: bool,
should_close: bool,
automatic_close: bool,
is_capturing_cursor: bool,
last_cursor_pos: Option<[f64; 2]>,
mouse_relative: Option<(f64, f64)>,
last_key_pressed: Option<input::Key>,
events: VecDeque<Event>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum UserEvent {
WakeUp,
}
impl WinitWindow {
pub fn new(settings: &WindowSettings) -> Self {
let event_loop = EventLoop::with_user_event().build().unwrap();
let mut w = WinitWindow {
event_loop: Some(event_loop),
keyboard_ignore_modifiers: KeyboardIgnoreModifiers::None,
window: None,
settings: settings.clone(),
should_close: false,
automatic_close: settings.get_automatic_close(),
events: VecDeque::new(),
last_key_pressed: None,
is_capturing_cursor: false,
last_cursor_pos: None,
mouse_relative: None,
title: settings.get_title(),
exit_on_esc: settings.get_exit_on_esc(),
devices: 0,
device_id_map: FxHashMap::default(),
};
if let Some(e) = w.poll_event() {w.events.push_front(e)}
w
}
pub fn get_window_ref(&self) -> &winit::window::Window {
self.window.as_ref().unwrap()
}
pub fn get_window(&self) -> Arc<winit::window::Window> {
self.window.as_ref().unwrap().clone()
}
fn pre_pop_front_event(&mut self) -> Option<Input> {
use input::Motion;
if let Some((x, y)) = self.mouse_relative {
self.mouse_relative = None;
return Some(Input::Move(Motion::MouseRelative([x, y])));
}
None
}
fn handle_event(
&mut self,
event: winit::event::WindowEvent,
unknown: &mut bool,
) -> Option<Input> {
use winit::keyboard::{Key, NamedKey};
match event {
WindowEvent::KeyboardInput { event: ref ev, .. } => {
if self.exit_on_esc {
if let Key::Named(NamedKey::Escape) = ev.logical_key {
self.set_should_close(true);
return None;
}
}
if let Some(s) = &ev.text {
let s = s.to_string();
let repeat = ev.repeat;
if !repeat {
if let Some(input) = map_window_event(
event,
self.get_window_ref().scale_factor(),
self.keyboard_ignore_modifiers,
unknown,
&mut self.last_key_pressed,
&mut self.devices,
&mut self.device_id_map,
) {
self.events.push_back(Event::Input(input, None));
}
}
return Some(Input::Text(s));
}
}
WindowEvent::CursorMoved { position, .. } => {
let scale = self.get_window_ref().scale_factor();
let position = position.to_logical::<f64>(scale);
let x = f64::from(position.x);
let y = f64::from(position.y);
let pre_event = self.pre_pop_front_event();
let mut input = || {
if let Some(pos) = self.last_cursor_pos {
let dx = x - pos[0];
let dy = y - pos[1];
if self.is_capturing_cursor {
self.last_cursor_pos = Some([x, y]);
self.fake_capture();
return Some(Input::Move(Motion::MouseRelative([dx as f64, dy as f64])));
}
self.mouse_relative = Some((dx as f64, dy as f64));
} else if self.is_capturing_cursor {
self.last_cursor_pos = Some([x, y]);
return None;
}
self.last_cursor_pos = Some([x, y]);
return Some(Input::Move(Motion::MouseCursor([x, y])))
};
let input = input();
return if pre_event.is_some() {
if let Some(input) = input {
self.events.push_back(Event::Input(input, None));
}
pre_event
} else {input}
}
_ => {}
}
let input = map_window_event(
event,
self.get_window_ref().scale_factor(),
self.keyboard_ignore_modifiers,
unknown,
&mut self.last_key_pressed,
&mut self.devices,
&mut self.device_id_map,
);
let pre_event = self.pre_pop_front_event();
if pre_event.is_some() {
if let Some(input) = input {
self.events.push_back(Event::Input(input, None));
}
pre_event
} else {input}
}
fn fake_capture(&mut self) {
if let Some(pos) = self.last_cursor_pos {
let size = self.size();
let cx = size.width / 2.0;
let cy = size.height / 2.0;
let dx = cx - pos[0];
let dy = cy - pos[1];
if dx != 0.0 || dy != 0.0 {
let pos = winit::dpi::LogicalPosition::new(cx, cy);
if let Ok(_) = self.get_window_ref().set_cursor_position(pos) {
self.last_cursor_pos = Some([cx, cy]);
}
}
}
}
}
impl Window for WinitWindow {
fn set_should_close(&mut self, value: bool) {
self.should_close = value;
}
fn should_close(&self) -> bool {
self.should_close
}
fn size(&self) -> Size {
let window = self.get_window_ref();
let (w, h): (u32, u32) = window.inner_size().into();
let hidpi = window.scale_factor();
((w as f64 / hidpi) as u32, (h as f64 / hidpi) as u32).into()
}
fn swap_buffers(&mut self) {}
fn wait_event(&mut self) -> Event {
use winit::platform::pump_events::EventLoopExtPumpEvents;
use input::{IdleArgs, Loop};
if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) {
let event_loop_proxy = event_loop.create_proxy();
event_loop_proxy
.send_event(UserEvent::WakeUp)
.expect("Event loop is closed before property handling all events.");
event_loop.pump_app_events(None, self);
self.event_loop = Some(event_loop);
}
let event = self.events.pop_front();
if let &Some(Event::Input(Input::Close(_), ..)) = &event {
self.set_should_close(true);
}
event.unwrap_or(Event::Loop(Loop::Idle(IdleArgs {dt: 0.0})))
}
fn wait_event_timeout(&mut self, timeout: Duration) -> Option<Event> {
use winit::platform::pump_events::EventLoopExtPumpEvents;
if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) {
let event_loop_proxy = event_loop.create_proxy();
event_loop_proxy
.send_event(UserEvent::WakeUp)
.expect("Event loop is closed before property handling all events.");
event_loop.pump_app_events(Some(timeout), self);
self.event_loop = Some(event_loop);
}
let event = self.events.pop_front();
if let &Some(Event::Input(Input::Close(_), ..)) = &event {
self.set_should_close(true);
}
event
}
fn poll_event(&mut self) -> Option<Event> {
use winit::platform::pump_events::EventLoopExtPumpEvents;
if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) {
let event_loop_proxy = event_loop.create_proxy();
event_loop_proxy
.send_event(UserEvent::WakeUp)
.expect("Event loop is closed before property handling all events.");
event_loop.pump_app_events(Some(Duration::ZERO), self);
self.event_loop = Some(event_loop);
}
let event = self.events.pop_front();
if let &Some(Event::Input(Input::Close(_), ..)) = &event {
self.set_should_close(true);
}
event
}
fn draw_size(&self) -> Size {
let size: (f64, f64) = self.get_window_ref().inner_size().into();
size.into()
}
}
impl ApplicationHandler<UserEvent> for WinitWindow {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let settings = &self.settings;
let window = event_loop.create_window(winit::window::Window::default_attributes()
.with_inner_size(LogicalSize::<f64>::new(
settings.get_size().width.into(),
settings.get_size().height.into(),
))
.with_title(settings.get_title())
).unwrap();
self.window = Some(Arc::new(window));
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let window = &self.get_window_ref();
match event {
WindowEvent::CloseRequested => {
if self.automatic_close {
self.should_close = true;
event_loop.exit();
}
}
WindowEvent::RedrawRequested => {
window.request_redraw();
},
event => {
let mut unknown = false;
if let Some(ev) = self.handle_event(event, &mut unknown) {
if !unknown {
self.events.push_back(Event::Input(ev, None));
}
}
}
}
}
}
impl AdvancedWindow for WinitWindow {
fn get_title(&self) -> String {
self.title.clone()
}
fn set_title(&mut self, value: String) {
self.get_window_ref().set_title(&value);
self.title = value;
}
fn get_exit_on_esc(&self) -> bool {
self.exit_on_esc
}
fn set_exit_on_esc(&mut self, value: bool) {
self.exit_on_esc = value
}
fn set_capture_cursor(&mut self, value: bool) {
self.is_capturing_cursor = value;
self.get_window_ref().set_cursor_visible(!value);
if value {
self.fake_capture();
}
}
fn get_automatic_close(&self) -> bool {self.automatic_close}
fn set_automatic_close(&mut self, value: bool) {self.automatic_close = value}
fn show(&mut self) {
self.get_window_ref().set_visible(true);
}
fn hide(&mut self) {
self.get_window_ref().set_visible(false);
}
fn get_position(&self) -> Option<Position> {
self.get_window_ref()
.outer_position()
.map(|p| Position { x: p.x, y: p.y })
.ok()
}
fn set_position<P: Into<Position>>(&mut self, val: P) {
let val = val.into();
self.get_window_ref()
.set_outer_position(LogicalPosition::new(val.x as f64, val.y as f64))
}
fn set_size<S: Into<Size>>(&mut self, size: S) {
let size: Size = size.into();
let w = self.get_window_ref();
let hidpi = w.scale_factor();
let _ = w.request_inner_size(LogicalSize::new(
size.width as f64 * hidpi,
size.height as f64 * hidpi,
));
}
}
impl BuildFromWindowSettings for WinitWindow {
fn build_from_window_settings(settings: &WindowSettings) -> Result<Self, Box<dyn Error>> {
Ok(Self::new(settings))
}
}
fn map_key(input: &winit::event::KeyEvent, kim: KeyboardIgnoreModifiers) -> Key {
use winit::keyboard::NamedKey::*;
use winit::keyboard::Key::*;
use KeyboardIgnoreModifiers as KIM;
match input.logical_key {
Character(ref ch) => match ch.as_str() {
"0" | ")" if kim == KIM::AbcKeyCode => Key::D0,
"0" => Key::D0,
")" => Key::RightParen,
"1" | "!" if kim == KIM::AbcKeyCode => Key::D1,
"1" => Key::D1,
"!" => Key::NumPadExclam,
"2" | "@" if kim == KIM::AbcKeyCode => Key::D2,
"2" => Key::D2,
"@" => Key::At,
"3" | "#" if kim == KIM::AbcKeyCode => Key::D3,
"3" => Key::D3,
"#" => Key::Hash,
"4" | "$" if kim == KIM::AbcKeyCode => Key::D4,
"4" => Key::D4,
"$" => Key::Dollar,
"5" | "%" if kim == KIM::AbcKeyCode => Key::D5,
"5" => Key::D5,
"%" => Key::Percent,
"6" | "^" if kim == KIM::AbcKeyCode => Key::D6,
"6" => Key::D6,
"^" => Key::Caret,
"7" | "&" if kim == KIM::AbcKeyCode => Key::D7,
"7" => Key::D7,
"&" => Key::Ampersand,
"8" | "*" if kim == KIM::AbcKeyCode => Key::D8,
"8" => Key::D8,
"*" => Key::Asterisk,
"9" | "(" if kim == KIM::AbcKeyCode => Key::D9,
"9" => Key::D9,
"(" => Key::LeftParen,
"a" | "A" => Key::A,
"b" | "B" => Key::B,
"c" | "C" => Key::C,
"d" | "D" => Key::D,
"e" | "E" => Key::E,
"f" | "F" => Key::F,
"g" | "G" => Key::G,
"h" | "H" => Key::H,
"i" | "I" => Key::I,
"j" | "J" => Key::J,
"k" | "K" => Key::K,
"l" | "L" => Key::L,
"m" | "M" => Key::M,
"n" | "N" => Key::N,
"o" | "O" => Key::O,
"p" | "P" => Key::P,
"q" | "Q" => Key::Q,
"r" | "R" => Key::R,
"s" | "S" => Key::S,
"t" | "T" => Key::T,
"u" | "U" => Key::U,
"v" | "V" => Key::V,
"w" | "W" => Key::W,
"x" | "X" => Key::X,
"y" | "Y" => Key::Y,
"z" | "Z" => Key::Z,
"'" | "\"" if kim == KIM::AbcKeyCode => Key::Quote,
"'" => Key::Quote,
"\"" => Key::Quotedbl,
";" | ":" if kim == KIM::AbcKeyCode => Key::Semicolon,
";" => Key::Semicolon,
":" => Key::Colon,
"[" | "{" if kim == KIM::AbcKeyCode => Key::LeftBracket,
"[" => Key::LeftBracket,
"{" => Key::NumPadLeftBrace,
"]" | "}" if kim == KIM::AbcKeyCode => Key::RightBracket,
"]" => Key::RightBracket,
"}" => Key::NumPadRightBrace,
"\\" | "|" if kim == KIM::AbcKeyCode => Key::Backslash,
"\\" => Key::Backslash,
"|" => Key::NumPadVerticalBar,
"," | "<" if kim == KIM::AbcKeyCode => Key::Comma,
"," => Key::Comma,
"<" => Key::Less,
"." | ">" if kim == KIM::AbcKeyCode => Key::Period,
"." => Key::Period,
">" => Key::Greater,
"/" | "?" if kim == KIM::AbcKeyCode => Key::Slash,
"/" => Key::Slash,
"?" => Key::Question,
"`" | "~" if kim == KIM::AbcKeyCode => Key::Backquote,
"`" => Key::Backquote,
"~" => Key::Unknown,
_ => Key::Unknown,
}
Named(Escape) => Key::Escape,
Named(F1) => Key::F1,
Named(F2) => Key::F2,
Named(F3) => Key::F3,
Named(F4) => Key::F4,
Named(F5) => Key::F5,
Named(F6) => Key::F6,
Named(F7) => Key::F7,
Named(F8) => Key::F8,
Named(F9) => Key::F9,
Named(F10) => Key::F10,
Named(F11) => Key::F11,
Named(F12) => Key::F12,
Named(F13) => Key::F13,
Named(F14) => Key::F14,
Named(F15) => Key::F15,
Named(Delete) => Key::Delete,
Named(ArrowLeft) => Key::Left,
Named(ArrowUp) => Key::Up,
Named(ArrowRight) => Key::Right,
Named(ArrowDown) => Key::Down,
Named(Backspace) => Key::Backspace,
Named(Enter) => Key::Return,
Named(Space) => Key::Space,
Named(Alt) => Key::LAlt,
Named(AltGraph) => Key::RAlt,
Named(Control) => Key::LCtrl,
Named(Super) => Key::Menu,
Named(Shift) => Key::LShift,
Named(Tab) => Key::Tab,
_ => Key::Unknown,
}
}
fn map_keyboard_input(
input: &winit::event::KeyEvent,
kim: KeyboardIgnoreModifiers,
unknown: &mut bool,
last_key_pressed: &mut Option<Key>,
) -> Option<Input> {
let key = map_key(input, kim);
let state = if input.state == ElementState::Pressed {
if let Some(last_key) = &*last_key_pressed {
if last_key == &key {
*unknown = true;
return None;
}
}
*last_key_pressed = Some(key);
ButtonState::Press
} else {
if let Some(last_key) = &*last_key_pressed {
if last_key == &key {
*last_key_pressed = None;
}
}
ButtonState::Release
};
Some(Input::Button(ButtonArgs {
state: state,
button: Button::Keyboard(key),
scancode: if let winit::keyboard::PhysicalKey::Code(code) = input.physical_key {
Some(code as i32)
} else {None},
}))
}
pub fn map_mouse(mouse_button: winit::event::MouseButton) -> MouseButton {
use winit::event::MouseButton as M;
match mouse_button {
M::Left => MouseButton::Left,
M::Right => MouseButton::Right,
M::Middle => MouseButton::Middle,
M::Other(0) => MouseButton::X1,
M::Other(1) => MouseButton::X2,
M::Other(2) => MouseButton::Button6,
M::Other(3) => MouseButton::Button7,
M::Other(4) => MouseButton::Button8,
_ => MouseButton::Unknown
}
}
fn map_window_event(
window_event: WindowEvent,
scale_factor: f64,
kim: KeyboardIgnoreModifiers,
unknown: &mut bool,
last_key_pressed: &mut Option<Key>,
devices: &mut u32,
device_id_map: &mut FxHashMap<DeviceId, u32>,
) -> Option<Input> {
use input::FileDrag;
match window_event {
WindowEvent::DroppedFile(path) =>
Some(Input::FileDrag(FileDrag::Drop(path))),
WindowEvent::HoveredFile(path) =>
Some(Input::FileDrag(FileDrag::Hover(path))),
WindowEvent::HoveredFileCancelled =>
Some(Input::FileDrag(FileDrag::Cancel)),
WindowEvent::Resized(size) => Some(Input::Resize(ResizeArgs {
window_size: [size.width as f64, size.height as f64],
draw_size: Size {
width: size.width as f64,
height: size.height as f64,
}
.into(),
})),
WindowEvent::CloseRequested => Some(Input::Close(CloseArgs)),
WindowEvent::Destroyed => Some(Input::Close(CloseArgs)),
WindowEvent::Focused(focused) => Some(Input::Focus(focused)),
WindowEvent::KeyboardInput { ref event, .. } => {
map_keyboard_input(event, kim, unknown, last_key_pressed)
}
WindowEvent::CursorMoved { position, .. } => {
let position = position.to_logical(scale_factor);
Some(Input::Move(Motion::MouseCursor([position.x, position.y])))
}
WindowEvent::CursorEntered { .. } => Some(Input::Cursor(true)),
WindowEvent::CursorLeft { .. } => Some(Input::Cursor(false)),
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::PixelDelta(position) => {
let position = position.to_logical(scale_factor);
Some(Input::Move(Motion::MouseScroll([position.x, position.y])))
}
MouseScrollDelta::LineDelta(x, y) =>
Some(Input::Move(Motion::MouseScroll([x as f64, y as f64]))),
},
WindowEvent::MouseInput { state, button, .. } => {
let button = map_mouse(button);
let state = match state {
ElementState::Pressed => ButtonState::Press,
ElementState::Released => ButtonState::Release,
};
Some(Input::Button(ButtonArgs {
state,
button: Button::Mouse(button),
scancode: None,
}))
}
WindowEvent::AxisMotion { device_id, axis, value } => {
use input::ControllerAxisArgs;
Some(Input::Move(Motion::ControllerAxis(ControllerAxisArgs::new(
{
if let Some(id) = device_id_map.get(&device_id) {*id}
else {
let id = *devices;
*devices += 1;
device_id_map.insert(device_id, id);
id
}
},
axis as u8,
value,
))))
}
WindowEvent::Touch(winit::event::Touch { phase, location, id, .. }) => {
use winit::event::TouchPhase;
use input::{Touch, TouchArgs};
let location = location.to_logical::<f64>(scale_factor);
Some(Input::Move(Motion::Touch(TouchArgs::new(
0, id as i64, [location.x, location.y], 1.0, match phase {
TouchPhase::Started => Touch::Start,
TouchPhase::Moved => Touch::Move,
TouchPhase::Ended => Touch::End,
TouchPhase::Cancelled => Touch::Cancel
}
))))
}
WindowEvent::TouchpadPressure { .. } |
WindowEvent::PinchGesture { .. } |
WindowEvent::RotationGesture { .. } |
WindowEvent::PanGesture { .. } |
WindowEvent::DoubleTapGesture { .. } => None,
WindowEvent::ScaleFactorChanged { .. } => None,
WindowEvent::ActivationTokenDone { .. } => None,
WindowEvent::ThemeChanged(_) => None,
WindowEvent::Ime(_) => None,
WindowEvent::Occluded(_) => None,
WindowEvent::RedrawRequested { .. } => None,
WindowEvent::Moved(_) => None,
WindowEvent::ModifiersChanged(_) => None,
}
}