use windows::Win32::{
Foundation::{HINSTANCE, HWND, LPARAM, RECT, WPARAM},
System::SystemServices::{
MK_LBUTTON,
MK_MBUTTON,
MK_RBUTTON,
MK_XBUTTON1,
MK_XBUTTON2,
MODIFIERKEYS_FLAGS,
},
UI::{
Input::KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY},
WindowsAndMessaging::{self, GetClientRect},
},
};
use super::{
command::Command,
data::{PhysicalPosition, PhysicalSize},
input::{mouse::MouseButton, state::RawKeyState},
};
use crate::{
utilities::{hi_word, is_flag_set, lo_byte, lo_word, signed_hi_word, signed_lo_word},
window::input::{
key::Key,
state::{ButtonState, KeyState},
},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Focus {
Gained,
Lost,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Message {
Loop(LoopMessage),
RawInput(RawInputMessage),
Created { hwnd: HWND, hinstance: HINSTANCE },
CloseRequested,
Paint,
Key {
key: Key,
state: KeyState,
scan_code: u16,
is_extended_key: bool,
},
Text(String),
ModifiersChanged {
shift: ButtonState,
ctrl: ButtonState,
alt: ButtonState,
win: ButtonState,
},
MouseButton {
button: MouseButton,
state: ButtonState,
position: PhysicalPosition,
is_double_click: bool,
},
MouseWheel { delta_x: f32, delta_y: f32 },
CursorMove {
position: PhysicalPosition,
kind: CursorMoveKind,
},
Resized(PhysicalSize),
Moved(PhysicalPosition),
BoundsChanged {
outer_position: PhysicalPosition,
outer_size: PhysicalSize,
},
Command,
SystemCommand,
Focus(Focus),
ScaleFactorChanged(f64),
}
#[derive(Debug, PartialEq, Clone)]
pub enum LoopMessage {
Command(Command),
Empty,
Exit,
}
#[derive(Debug, PartialEq, Clone)]
pub enum RawInputMessage {
Keyboard { key: Key, state: RawKeyState },
MouseButton {
button: MouseButton,
state: ButtonState,
},
MouseMove { delta_x: f32, delta_y: f32 },
}
impl Message {
pub(crate) fn new_keyboard_message(l_param: LPARAM) -> Message {
let flags = hi_word(unsafe { std::mem::transmute::<i32, u32>(l_param.0 as i32) });
let is_extended_key = is_flag_set(flags, WindowsAndMessaging::KF_EXTENDED as u16);
let mut scan_code = lo_byte(flags) as u16;
let key_code: Key = {
let extended_scan_code = u16::from_le_bytes([scan_code as u8, 0xE0]);
let extended_virtual_keycode = VIRTUAL_KEY(lo_word(unsafe {
MapVirtualKeyW(extended_scan_code as u32, MAPVK_VSC_TO_VK_EX)
}));
let virtual_keycode =
if extended_virtual_keycode != VIRTUAL_KEY(0) && is_extended_key {
scan_code = extended_scan_code;
extended_virtual_keycode
} else {
VIRTUAL_KEY(lo_word(unsafe {
MapVirtualKeyW(scan_code as u32, MAPVK_VSC_TO_VK_EX)
}))
};
virtual_keycode.into()
};
let state = {
let repeat_count = lo_word(l_param.0 as u32);
let was_key_down = is_flag_set(flags, WindowsAndMessaging::KF_REPEAT as u16);
let is_key_up = is_flag_set(flags, WindowsAndMessaging::KF_UP as u16);
match (is_key_up, was_key_down) {
(true, _) => KeyState::Released,
(false, true) => KeyState::Held(repeat_count),
(..) => KeyState::Pressed,
}
};
Message::Key {
key: key_code,
state,
scan_code,
is_extended_key,
}
}
pub(crate) fn new_mouse_button_message(
message: u32,
w_param: WPARAM,
l_param: LPARAM,
) -> Message {
let flags = w_param.0 as u32;
let mouse_code: MouseButton = {
match message {
WindowsAndMessaging::WM_LBUTTONDBLCLK
| WindowsAndMessaging::WM_LBUTTONDOWN
| WindowsAndMessaging::WM_LBUTTONUP => MouseButton::Left,
WindowsAndMessaging::WM_MBUTTONDBLCLK
| WindowsAndMessaging::WM_MBUTTONDOWN
| WindowsAndMessaging::WM_MBUTTONUP => MouseButton::Middle,
WindowsAndMessaging::WM_RBUTTONDBLCLK
| WindowsAndMessaging::WM_RBUTTONDOWN
| WindowsAndMessaging::WM_RBUTTONUP => MouseButton::Right,
WindowsAndMessaging::WM_XBUTTONDBLCLK
| WindowsAndMessaging::WM_XBUTTONDOWN
| WindowsAndMessaging::WM_XBUTTONUP => {
let hi_flags = hi_word(flags);
if (hi_flags & WindowsAndMessaging::XBUTTON1) == WindowsAndMessaging::XBUTTON1 {
MouseButton::Back
} else {
MouseButton::Forward
}
}
_ => MouseButton::Unknown,
}
};
let is_double_click = matches!(
message,
WindowsAndMessaging::WM_LBUTTONDBLCLK
| WindowsAndMessaging::WM_MBUTTONDBLCLK
| WindowsAndMessaging::WM_RBUTTONDBLCLK
| WindowsAndMessaging::WM_XBUTTONDBLCLK
);
let state = {
let mod_flags = MODIFIERKEYS_FLAGS(flags);
let is_l_down = (mod_flags & MK_LBUTTON) == MK_LBUTTON;
let is_m_down = (mod_flags & MK_MBUTTON) == MK_MBUTTON;
let is_r_down = (mod_flags & MK_RBUTTON) == MK_RBUTTON;
let is_x1_down = (mod_flags & MK_XBUTTON1) == MK_XBUTTON1;
let is_x2_down = (mod_flags & MK_XBUTTON2) == MK_XBUTTON2;
let is_down = match message {
WindowsAndMessaging::WM_LBUTTONDBLCLK | WindowsAndMessaging::WM_LBUTTONDOWN
if is_l_down =>
{
true
}
WindowsAndMessaging::WM_MBUTTONDBLCLK | WindowsAndMessaging::WM_MBUTTONDOWN
if is_m_down =>
{
true
}
WindowsAndMessaging::WM_RBUTTONDBLCLK | WindowsAndMessaging::WM_RBUTTONDOWN
if is_r_down =>
{
true
}
WindowsAndMessaging::WM_XBUTTONDBLCLK | WindowsAndMessaging::WM_XBUTTONDOWN
if is_x1_down || is_x2_down =>
{
true
}
_ => false,
};
if is_down {
ButtonState::Pressed
} else {
ButtonState::Released
}
};
let (x, y) = (signed_lo_word(l_param.0 as i32), signed_hi_word(l_param.0 as i32));
let position = PhysicalPosition::new(x as i32, y as i32);
Message::MouseButton {
button: mouse_code,
state,
position,
is_double_click,
}
}
pub fn is_key(&self, key: Key, state: KeyState) -> bool {
matches!(self, Message::Key { key: k, state: s, .. } if *k == key && *s == state)
}
pub fn is_mouse_button(&self, button: MouseButton, state: ButtonState) -> bool {
matches!(self, Message::MouseButton { button: b, state: s, .. } if *b == button && *s == state)
}
pub fn is_empty(&self) -> bool {
matches!(self, Message::Loop(LoopMessage::Empty))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CursorMoveKind {
Entered,
Left,
Inside,
}
pub(crate) fn get_cursor_move_kind(
hwnd: HWND,
mouse_was_inside_window: bool,
x: i32,
y: i32,
) -> CursorMoveKind {
let rect: RECT = {
let mut rect = RECT::default();
if unsafe { GetClientRect(hwnd, &mut rect) }.is_err() {
return CursorMoveKind::Inside; }
rect
};
let x = (rect.left..rect.right).contains(&x);
let y = (rect.top..rect.bottom).contains(&y);
if !mouse_was_inside_window && x && y {
CursorMoveKind::Entered
} else if mouse_was_inside_window && !(x && y) {
CursorMoveKind::Left
} else {
CursorMoveKind::Inside
}
}