use bitflags::bitflags;
#[derive(Default, Debug, Clone, Copy)]
pub struct Position {
pub x: i32,
pub y: i32,
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum MouseButton {
Left,
Middle,
Right,
}
#[derive(Debug, Clone)]
pub enum MouseEvent {
Click(MouseButton, bool, Option<Position>),
Move(Position),
Wheel(Position),
}
#[derive(Debug)]
pub enum IMEAction<'a> {
Composition(&'a str),
Pre(&'a str, i32, i32),
}
bitflags! {
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct KeyboardModifiers: u8 {
const None = 0;
const Shift = 1;
const Ctrl = 2;
const Alt = 4;
const Win = 8;
const Command = 16;
const CapsLock = 32;
}
}
impl Default for KeyboardModifiers {
fn default() -> Self {
Self::None
}
}
#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum KeyboardEventType {
#[default]
KeyDown,
KeyUp,
Char,
}
#[derive(Default, Debug, Copy, Clone)]
pub struct KeyboardEvent {
pub ty: KeyboardEventType,
pub modifiers: KeyboardModifiers,
pub windows_key_code: u32,
pub native_key_code: u32,
pub is_system_key: u32,
pub character: u16,
pub unmodified_character: u16,
pub focus_on_editable_field: bool,
}
#[allow(unused)]
#[derive(Default)]
pub struct EventAdapter {
modifiers: KeyboardModifiers,
allow_ime: bool,
}
#[cfg(feature = "winit")]
mod winit_impl {
use winit::{
event::{Ime, MouseButton as WinitMouseButton, MouseScrollDelta, WindowEvent},
keyboard::{Key, KeyCode, ModifiersState, PhysicalKey},
platform::{
modifier_supplement::KeyEventExtModifierSupplement, scancode::PhysicalKeyExtScancode,
},
};
use crate::{
WindowlessRenderWebView,
events::{
EventAdapter, IMEAction, KeyboardEvent, KeyboardEventType, KeyboardModifiers,
MouseButton, MouseEvent, Position,
},
webview::WebView,
};
#[cfg(target_os = "windows")]
use windows::Win32::UI::Input::KeyboardAndMouse::{
GetKeyState, MAPVK_VSC_TO_VK_EX, MapVirtualKeyA, VK_CAPITAL,
};
impl EventAdapter {
#[inline]
#[cfg(target_os = "windows")]
fn get_capslock_state() -> bool {
return (unsafe { GetKeyState(VK_CAPITAL.0 as i32) } & 0x0001) != 0;
}
pub fn on_winit_window_event(
&mut self,
webview: &WebView<WindowlessRenderWebView>,
event: &WindowEvent,
) {
match event {
WindowEvent::Ime(ime) => match ime {
Ime::Commit(composition) => {
webview.ime(&IMEAction::Composition(composition));
}
Ime::Preedit(preedit, Some((cursor_pos, selection_start))) => {
webview.ime(&IMEAction::Pre(
preedit,
*cursor_pos as i32,
*selection_start as i32,
));
}
Ime::Enabled => {
self.allow_ime = true;
if cfg!(target_os = "windows") {
let mut event = KeyboardEvent {
ty: KeyboardEventType::KeyDown,
modifiers: KeyboardModifiers::None,
windows_key_code: 8,
native_key_code: 14,
is_system_key: 0,
character: 8,
unmodified_character: 8,
focus_on_editable_field: false,
};
webview.keyboard(&event);
event.ty = KeyboardEventType::KeyUp;
webview.keyboard(&event);
}
}
Ime::Disabled => {
self.allow_ime = false;
}
_ => (),
},
WindowEvent::ModifiersChanged(modifiers) => {
self.modifiers = KeyboardModifiers::None;
let state = modifiers.state();
for it in ModifiersState::all() {
if state.contains(it) {
self.modifiers |= KeyboardModifiers::from(it);
}
}
#[cfg(target_os = "windows")]
if Self::get_capslock_state() {
self.modifiers |= KeyboardModifiers::CapsLock;
}
}
WindowEvent::KeyboardInput { event: input, .. } => {
let mut event = KeyboardEvent::default();
event.ty = if input.state.is_pressed() {
KeyboardEventType::KeyDown
} else {
KeyboardEventType::KeyUp
};
let key_code = if let PhysicalKey::Code(code) = input.physical_key {
event.native_key_code = code.to_scancode().unwrap_or(0);
code
} else {
return;
};
if let Some(text) = input.text.as_ref().map(|it| it.as_str()).or_else(|| {
if let Key::Character(text) = &input.logical_key {
Some(text.as_str())
} else {
input.text_with_all_modifiers()
}
}) {
if let Some(character) = text.chars().next() {
event.unmodified_character = character as u16;
event.character = character as u16;
}
}
if cfg!(target_os = "windows") {
if self.allow_ime {
return;
}
if !input.state.is_pressed() {
if let PhysicalKey::Code(KeyCode::CapsLock) = input.physical_key {
self.modifiers |= KeyboardModifiers::CapsLock;
}
}
#[cfg(target_os = "windows")]
{
event.windows_key_code = unsafe {
MapVirtualKeyA(event.native_key_code, MAPVK_VSC_TO_VK_EX)
};
}
event.modifiers = self.modifiers;
webview.keyboard(&event);
if input.state.is_pressed() && is_char(&key_code) {
if let Some((base, upcase)) = get_symbol_mapping(&key_code) {
event.windows_key_code =
if self.modifiers.contains(KeyboardModifiers::Shift) {
*upcase as u32
} else {
*base as u32
};
} else {
if key_code != KeyCode::Space
&& !self.modifiers.contains(KeyboardModifiers::CapsLock)
&& !self.modifiers.contains(KeyboardModifiers::Shift)
{
event.windows_key_code += 32;
}
}
event.ty = KeyboardEventType::Char;
webview.keyboard(&event);
}
} else {
event.modifiers = self.modifiers;
if let Some(modifiers) = match key_code {
KeyCode::CapsLock => Some(KeyboardModifiers::CapsLock),
KeyCode::AltLeft | KeyCode::AltRight => Some(KeyboardModifiers::Alt),
KeyCode::ControlLeft | KeyCode::ControlRight => {
Some(KeyboardModifiers::Ctrl)
}
KeyCode::ShiftLeft | KeyCode::ShiftRight => {
Some(KeyboardModifiers::Shift)
}
KeyCode::SuperLeft | KeyCode::SuperRight => {
Some(KeyboardModifiers::Command)
}
_ => None,
} {
if input.state.is_pressed() {
event.modifiers |= modifiers;
} else {
event.modifiers.remove(modifiers);
}
}
if cfg!(target_os = "linux") {
if key_code == KeyCode::Backspace || key_code == KeyCode::Enter {
event.windows_key_code = event.character as u32;
}
}
webview.keyboard(&event);
if input.state.is_pressed() {
event.ty = KeyboardEventType::Char;
webview.keyboard(&event);
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
webview.mouse(&MouseEvent::Click(
MouseButton::from(*button),
state.is_pressed(),
None,
));
}
WindowEvent::MouseWheel { delta, .. } => {
let (x, y) = match delta {
MouseScrollDelta::PixelDelta(pos) => (pos.x as i32, pos.y as i32),
MouseScrollDelta::LineDelta(x, y) => ((x * 20.0) as i32, (y * 20.0) as i32),
};
webview.mouse(&MouseEvent::Wheel(Position { x, y }));
}
WindowEvent::CursorMoved { position, .. } => {
webview.mouse(&MouseEvent::Move(Position {
x: position.x as i32,
y: position.y as i32,
}));
}
WindowEvent::Focused(state) => {
webview.focus(*state);
#[cfg(target_os = "windows")]
if *state && Self::get_capslock_state() {
self.modifiers |= KeyboardModifiers::CapsLock;
}
}
WindowEvent::Resized(size) => {
webview.resize(size.width, size.height);
}
_ => {}
}
}
}
impl From<ModifiersState> for KeyboardModifiers {
fn from(value: ModifiersState) -> Self {
match value {
ModifiersState::SHIFT => Self::Shift,
ModifiersState::CONTROL => Self::Ctrl,
ModifiersState::ALT => Self::Alt,
ModifiersState::SUPER => {
if cfg!(target_os = "macos") {
Self::Command
} else {
Self::Win
}
}
_ => Self::None,
}
}
}
impl From<WinitMouseButton> for MouseButton {
fn from(value: WinitMouseButton) -> Self {
match value {
WinitMouseButton::Left => Self::Left,
WinitMouseButton::Right => Self::Right,
WinitMouseButton::Middle => Self::Middle,
_ => Self::Middle,
}
}
}
#[inline]
fn get_symbol_mapping(code: &KeyCode) -> Option<&'static (char, char)> {
[
(KeyCode::Digit1, ('1', '!')),
(KeyCode::Digit2, ('2', '@')),
(KeyCode::Digit3, ('3', '#')),
(KeyCode::Digit4, ('4', '$')),
(KeyCode::Digit5, ('5', '%')),
(KeyCode::Digit6, ('6', '^')),
(KeyCode::Digit7, ('7', '&')),
(KeyCode::Digit8, ('8', '*')),
(KeyCode::Digit9, ('9', '(')),
(KeyCode::Digit0, ('0', ')')),
(KeyCode::Minus, ('-', '_')),
(KeyCode::Equal, ('=', '+')),
(KeyCode::BracketLeft, ('[', '{')),
(KeyCode::BracketRight, (']', '}')),
(KeyCode::Semicolon, (';', ':')),
(KeyCode::Quote, ('\'', '"')),
(KeyCode::Backquote, ('`', '~')),
(KeyCode::Backslash, ('\\', '|')),
(KeyCode::Comma, (',', '<')),
(KeyCode::Period, ('.', '>')),
(KeyCode::Slash, ('/', '?')),
]
.iter()
.find(|(v, _)| v == code)
.map(|(_, v)| v)
}
#[inline]
fn is_char(code: &KeyCode) -> bool {
[
KeyCode::Backquote,
KeyCode::Backslash,
KeyCode::BracketLeft,
KeyCode::BracketRight,
KeyCode::IntlBackslash,
KeyCode::Semicolon,
KeyCode::Comma,
KeyCode::Equal,
KeyCode::Minus,
KeyCode::Period,
KeyCode::Quote,
KeyCode::Slash,
KeyCode::Space,
KeyCode::Digit0,
KeyCode::Digit1,
KeyCode::Digit2,
KeyCode::Digit3,
KeyCode::Digit4,
KeyCode::Digit5,
KeyCode::Digit6,
KeyCode::Digit7,
KeyCode::Digit8,
KeyCode::Digit9,
KeyCode::KeyA,
KeyCode::KeyB,
KeyCode::KeyC,
KeyCode::KeyD,
KeyCode::KeyE,
KeyCode::KeyF,
KeyCode::KeyG,
KeyCode::KeyH,
KeyCode::KeyI,
KeyCode::KeyJ,
KeyCode::KeyK,
KeyCode::KeyL,
KeyCode::KeyM,
KeyCode::KeyN,
KeyCode::KeyO,
KeyCode::KeyP,
KeyCode::KeyQ,
KeyCode::KeyR,
KeyCode::KeyS,
KeyCode::KeyT,
KeyCode::KeyU,
KeyCode::KeyV,
KeyCode::KeyW,
KeyCode::KeyX,
KeyCode::KeyY,
KeyCode::KeyZ,
]
.contains(code)
}
}