#[cfg(feature = "raw_input")]
use crate::raw_input;
use crate::{
api::*,
context::*,
device::*,
event,
event::{EventHandler, ResizingEdge},
geometry::*,
ime,
window::{Style, Window},
};
use std::panic::catch_unwind;
use std::path::PathBuf;
use windows::core::HSTRING;
use windows::Win32::{
Foundation::*, Graphics::Gdi::*, UI::Controls::WM_MOUSELEAVE, UI::HiDpi::*, UI::Input::Ime::*,
UI::Input::KeyboardAndMouse::*, UI::Shell::*, UI::WindowsAndMessaging::*,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(usize)]
pub(crate) enum UserMessage {
SetTitle,
SetPosition,
SetInnerSize,
EnableIme,
DisableIme,
SetStyle,
AcceptDragFiles,
}
#[inline]
fn hiword(x: i32) -> i16 {
((x >> 16) & 0xffff) as _
}
#[inline]
fn get_x_lparam(lp: LPARAM) -> i16 {
(lp.0 & 0xffff) as _
}
#[inline]
fn get_y_lparam(lp: LPARAM) -> i16 {
((lp.0 >> 16) & 0xffff) as _
}
#[inline]
fn get_xbutton_wparam(wp: WPARAM) -> u16 {
((wp.0 >> 16) & 0xffff) as _
}
#[inline]
fn get_keystate_wparam(wp: WPARAM) -> u32 {
(wp.0 & 0xffff) as _
}
#[inline]
fn lparam_to_point(lparam: LPARAM) -> PhysicalPosition<i32> {
PhysicalPosition::new(get_x_lparam(lparam) as i32, get_y_lparam(lparam) as i32)
}
#[inline]
fn wparam_to_button(wparam: WPARAM) -> MouseButton {
match get_xbutton_wparam(wparam) {
0x0001 => MouseButton::Ex(0),
0x0002 => MouseButton::Ex(1),
_ => unreachable!(),
}
}
fn update_buttons(buttons: &mut Vec<MouseButton>, wparam: WPARAM) {
buttons.clear();
let values = get_keystate_wparam(wparam);
if values & MK_LBUTTON != 0 {
buttons.push(MouseButton::Left);
}
if values & MK_RBUTTON != 0 {
buttons.push(MouseButton::Right);
}
if values & MK_MBUTTON != 0 {
buttons.push(MouseButton::Middle);
}
if values & MK_XBUTTON1 != 0 {
buttons.push(MouseButton::Ex(0));
}
if values & MK_XBUTTON2 != 0 {
buttons.push(MouseButton::Ex(1));
}
}
fn mouse_input<T: EventHandler + 'static>(
window: &Window,
button: MouseButton,
button_state: KeyState,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
call_handler(|eh: &mut T, state| {
match button_state {
KeyState::Pressed => unsafe {
SetCapture(HWND(window.raw_handle() as _));
},
KeyState::Released => unsafe {
ReleaseCapture();
},
}
let buttons = &mut state.mouse_buttons;
update_buttons(buttons, wparam);
let ev = event::MouseInput {
window,
button,
button_state,
mouse_state: MouseState {
position: lparam_to_point(lparam),
buttons,
},
};
eh.mouse_input(ev);
});
LRESULT(0)
}
fn mouse_wheel<T: EventHandler + 'static>(
window: &Window,
dir: MouseWheelAxis,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
call_handler(|eh: &mut T, state| {
let delta = hiword(wparam.0 as _);
let buttons = &mut state.mouse_buttons;
update_buttons(buttons, wparam);
let ev = event::MouseWheel {
window,
axis: dir,
distance: delta as i32,
mouse_state: MouseState {
position: lparam_to_point(lparam),
buttons,
},
};
eh.mouse_wheel(ev);
});
LRESULT(0)
}
fn key_input<T: EventHandler + 'static>(
window: &Window,
state: KeyState,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
let scan_code = ScanCode(((lparam.0 >> 16) & 0x7f) as u32);
call_handler(|eh: &mut T, _| {
let ev = event::KeyInput {
window,
key_code: KeyCode::new(as_virtual_key(VIRTUAL_KEY(wparam.0 as _)), scan_code),
state,
prev_pressed: (lparam.0 >> 30) & 0x01 != 0,
};
eh.key_input(ev);
});
LRESULT(0)
}
pub(crate) extern "system" fn window_proc<T: EventHandler + 'static>(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
let ret = catch_unwind(|| unsafe {
let window = find_window(hwnd);
if window.is_none() {
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
let window = window.unwrap();
let handle = &window.handle;
match msg {
WM_PAINT => {
let mut ps = PAINTSTRUCT::default();
BeginPaint(hwnd, &mut ps);
call_handler(|eh: &mut T, _| eh.draw(event::Draw { window: handle }));
EndPaint(hwnd, &ps);
LRESULT(0)
}
#[cfg(feature = "raw_input")]
WM_INPUT => raw_input::wm_input::<T>(handle, hwnd, wparam, lparam),
WM_MOUSEMOVE => {
call_handler(|eh: &mut T, state| {
let position = lparam_to_point(lparam);
update_buttons(&mut state.mouse_buttons, wparam);
if state.entered_window.is_none() {
TrackMouseEvent(&mut TRACKMOUSEEVENT {
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as _,
dwFlags: TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: 0,
});
state.entered_window = Some(window.clone());
eh.cursor_entered(event::CursorEntered {
window: handle,
mouse_state: MouseState {
position,
buttons: &state.mouse_buttons,
},
});
} else {
eh.cursor_moved(event::CursorMoved {
window: handle,
mouse_state: MouseState {
position,
buttons: &state.mouse_buttons,
},
});
}
});
let wnd = handle.state.read().unwrap();
wnd.cursor.set();
LRESULT(0)
}
WM_MOUSELEAVE => {
call_handler(|eh: &mut T, state| {
state.entered_window = None;
update_buttons(&mut state.mouse_buttons, wparam);
let mut pos = POINT::default();
GetCursorPos(&mut pos);
eh.cursor_leaved(event::CursorLeaved {
window: handle,
mouse_state: MouseState {
position: PhysicalPosition::new(pos.x, pos.y),
buttons: &mut state.mouse_buttons,
},
});
});
LRESULT(0)
}
WM_LBUTTONDOWN => {
mouse_input::<T>(handle, MouseButton::Left, KeyState::Pressed, wparam, lparam)
}
WM_RBUTTONDOWN => mouse_input::<T>(
handle,
MouseButton::Right,
KeyState::Pressed,
wparam,
lparam,
),
WM_MBUTTONDOWN => mouse_input::<T>(
handle,
MouseButton::Middle,
KeyState::Pressed,
wparam,
lparam,
),
WM_XBUTTONDOWN => mouse_input::<T>(
handle,
wparam_to_button(wparam),
KeyState::Pressed,
wparam,
lparam,
),
WM_LBUTTONUP => mouse_input::<T>(
handle,
MouseButton::Left,
KeyState::Released,
wparam,
lparam,
),
WM_RBUTTONUP => mouse_input::<T>(
handle,
MouseButton::Right,
KeyState::Released,
wparam,
lparam,
),
WM_MBUTTONUP => mouse_input::<T>(
handle,
MouseButton::Middle,
KeyState::Released,
wparam,
lparam,
),
WM_XBUTTONUP => mouse_input::<T>(
handle,
wparam_to_button(wparam),
KeyState::Released,
wparam,
lparam,
),
WM_MOUSEWHEEL => mouse_wheel::<T>(handle, MouseWheelAxis::Vertical, wparam, lparam),
WM_MOUSEHWHEEL => mouse_wheel::<T>(handle, MouseWheelAxis::Horizontal, wparam, lparam),
WM_KEYDOWN => key_input::<T>(handle, KeyState::Pressed, wparam, lparam),
WM_KEYUP => key_input::<T>(handle, KeyState::Released, wparam, lparam),
WM_CHAR => {
call_handler(|eh: &mut T, _| {
if let Some(c) = std::char::from_u32(wparam.0 as u32) {
eh.char_input(event::CharInput { window: handle, c });
}
});
LRESULT(0)
}
WM_IME_SETCONTEXT => {
let lparam = {
let state = handle.state.read().unwrap();
let mut lparam = lparam.0 as u32;
if !state.visible_ime_composition_window {
lparam &= !ISC_SHOWUICOMPOSITIONWINDOW;
}
if !state.visible_ime_candidate_window {
lparam &= !ISC_SHOWUICANDIDATEWINDOW;
lparam &= !(ISC_SHOWUICANDIDATEWINDOW << 1);
lparam &= !(ISC_SHOWUICANDIDATEWINDOW << 2);
lparam &= !(ISC_SHOWUICANDIDATEWINDOW << 3);
}
LPARAM(lparam as _)
};
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_IME_STARTCOMPOSITION => {
{
let imc = ime::Imc::get(hwnd);
let state = handle.state.read().unwrap();
if state.visible_ime_composition_window {
imc.set_composition_window_position(state.ime_position);
}
if state.visible_ime_candidate_window {
imc.set_candidate_window_position(
state.ime_position,
state.visible_ime_composition_window,
);
}
}
call_handler(|eh: &mut T, _| {
eh.ime_start_composition(event::ImeStartComposition { window: handle });
});
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_IME_COMPOSITION => {
call_handler(|eh: &mut T, _| {
let imc = ime::Imc::get(hwnd);
if (lparam.0 as u32) & GCS_COMPSTR != 0 {
let s = imc.get_composition_string(GCS_COMPSTR);
let attrs = imc.get_composition_string(GCS_COMPATTR);
let clause = imc.get_composition_string(GCS_COMPCLAUSE);
if let (
Some(ime::CompositionString::CompStr(s)),
Some(ime::CompositionString::CompAttr(attrs)),
Some(ime::CompositionString::CompClause(clause)),
) = (s, attrs, clause)
{
eh.ime_composition(event::ImeComposition {
window: handle,
composition: &ime::Composition::new(s, attrs, clause),
candidate_list: imc.get_candidate_list().as_ref(),
});
}
}
if (lparam.0 as u32) & GCS_RESULTSTR != 0 {
let s = imc.get_composition_string(GCS_COMPSTR);
let attrs = imc.get_composition_string(GCS_COMPATTR);
let clause = imc.get_composition_string(GCS_COMPCLAUSE);
if let (
Some(ime::CompositionString::CompStr(s)),
Some(ime::CompositionString::CompAttr(attrs)),
Some(ime::CompositionString::CompClause(clause)),
) = (s, attrs, clause)
{
eh.ime_composition(event::ImeComposition {
window: handle,
composition: &ime::Composition::new(s, attrs, clause),
candidate_list: None,
});
}
}
});
let show_composition_window = {
let state = handle.state.read().unwrap();
state.visible_ime_composition_window
};
if show_composition_window {
DefWindowProcW(hwnd, msg, wparam, lparam)
} else {
LRESULT(0)
}
}
WM_IME_ENDCOMPOSITION => {
call_handler(|eh: &mut T, _| {
let imc = ime::Imc::get(hwnd);
let ret = imc.get_composition_string(GCS_RESULTSTR);
let ret = if let Some(ime::CompositionString::ResultStr(s)) = &ret {
Some(s.as_str())
} else {
None
};
let ev = event::ImeEndComposition {
window: handle,
result: ret,
};
eh.ime_end_composition(ev);
});
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_ACTIVATE => {
let active = ((wparam.0 as u32) & WA_ACTIVE) != 0;
let click_active = ((wparam.0 as u32) & WA_CLICKACTIVE) != 0;
if active || click_active {
call_handler(|eh: &mut T, _| eh.activated(event::Activated { window: handle }));
} else {
call_handler(|eh: &mut T, _| {
eh.inactivated(event::Inactivated { window: handle })
});
}
LRESULT(0)
}
WM_SIZING => {
set_resizing(true);
let d = {
let mut wrc = RECT::default();
GetWindowRect(hwnd, &mut wrc);
let mut crc = RECT::default();
GetClientRect(hwnd, &mut crc);
PhysicalSize::new(
(wrc.right - wrc.left) - (crc.right - crc.left),
(wrc.bottom - wrc.top) - (crc.bottom - crc.top),
)
};
let mut rc = (lparam.0 as *mut RECT).as_mut().unwrap();
let mut size = Size::new(
(rc.right - rc.left - d.width) as u32,
(rc.bottom - rc.top - d.height) as u32,
);
let edge = match wparam.0 as u32 {
WMSZ_LEFT => ResizingEdge::Left,
WMSZ_RIGHT => ResizingEdge::Right,
WMSZ_TOP => ResizingEdge::Top,
WMSZ_TOPLEFT => ResizingEdge::TopLeft,
WMSZ_TOPRIGHT => ResizingEdge::TopRight,
WMSZ_BOTTOM => ResizingEdge::Bottom,
WMSZ_BOTTOMLEFT => ResizingEdge::BottomLeft,
WMSZ_BOTTOMRIGHT => ResizingEdge::BottomRight,
_ => unreachable!(),
};
call_handler(|eh: &mut T, _| {
eh.resizing(event::Resizing {
window: handle,
size: &mut size,
edge,
});
});
let tmp = adjust_window_rect(
size,
handle.style().value(),
WINDOW_EX_STYLE(0),
handle.dpi(),
);
rc.right = rc.left + tmp.right - tmp.left;
rc.bottom = rc.top + tmp.bottom - tmp.top;
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_SIZE => {
match wparam.0 as u32 {
SIZE_MINIMIZED => {
call_handler(|eh: &mut T, _| {
eh.minimized(event::Minimized { window: handle });
});
set_minimized(true);
set_maximized(false);
}
SIZE_MAXIMIZED => {
call_handler(|eh: &mut T, _| {
eh.maximized(event::Maximized {
window: handle,
size: PhysicalSize::new(
get_x_lparam(lparam) as u32,
get_y_lparam(lparam) as u32,
),
});
});
set_minimized(false);
set_maximized(true);
}
SIZE_RESTORED => {
call_handler(|eh: &mut T, state| {
if state.minimized || state.maximized {
eh.restored(event::Restored {
window: handle,
size: PhysicalSize::new(
get_x_lparam(lparam) as u32,
get_y_lparam(lparam) as u32,
),
});
set_minimized(false);
set_maximized(false);
}
});
}
_ => {}
}
LRESULT(0)
}
WM_WINDOWPOSCHANGED => {
let pos = &*(lparam.0 as *const WINDOWPOS);
if pos.flags & SWP_NOMOVE == SET_WINDOW_POS_FLAGS(0) {
call_handler(|eh: &mut T, _| {
eh.moved(event::Moved {
window: handle,
position: ScreenPosition::new(pos.x, pos.y),
});
});
}
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_EXITSIZEMOVE => {
let size = handle.inner_size();
call_handler(|eh: &mut T, state| {
if state.resizing {
eh.resized(event::Resized {
window: handle,
size,
});
set_resizing(false);
}
});
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_DPICHANGED => {
let mut prev_rc = RECT::default();
GetWindowRect(hwnd, &mut prev_rc);
let rc = *(lparam.0 as *const RECT);
SetWindowPos(
hwnd,
HWND(0),
rc.left,
rc.top,
rc.right - rc.left,
rc.bottom - rc.top,
SWP_NOZORDER | SWP_NOACTIVATE,
);
let new_dpi = hiword(wparam.0 as _) as u32;
call_handler(|eh: &mut T, _| {
eh.dpi_changed(event::DpiChanged {
window: handle,
new_dpi,
})
});
let prev_size = prev_rc.right - prev_rc.left;
let next_size = rc.right - rc.left;
let children = child_windows(hwnd);
for child in children {
let child_rc = {
let mut rc = RECT::default();
let mut screen = RECT::default();
GetWindowRect(child, &mut screen);
let mut pt = POINT {
x: screen.left,
y: screen.top,
};
ScreenToClient(hwnd, &mut pt);
rc.left = pt.x;
rc.top = pt.y;
let mut pt = POINT {
x: screen.right,
y: screen.bottom,
};
ScreenToClient(hwnd, &mut pt);
rc.right = pt.x;
rc.bottom = pt.y;
rc
};
let pt = PhysicalPosition::new(
child_rc.left * next_size / prev_size,
child_rc.top * next_size / prev_size,
);
let size = PhysicalSize::new(
(child_rc.right - child_rc.left) * next_size / prev_size,
(child_rc.bottom - child_rc.top) * next_size / prev_size,
);
SetWindowPos(
child,
HWND(0),
pt.x,
pt.y,
size.width,
size.height,
SWP_NOACTIVATE | SWP_NOZORDER,
);
if let Some(child) = find_window(child) {
call_handler(|eh: &mut T, _| {
eh.dpi_changed(event::DpiChanged {
window: &child.handle,
new_dpi,
})
});
}
}
LRESULT(0)
}
WM_GETDPISCALEDSIZE => {
let prev_dpi = GetDpiForWindow(hwnd) as i32;
let next_dpi = wparam.0 as i32;
let mut rc = RECT::default();
GetClientRect(hwnd, &mut rc);
let size = PhysicalSize::new(
((rc.right - rc.left) * next_dpi / prev_dpi) as u32,
((rc.bottom - rc.top) * next_dpi / prev_dpi) as u32,
);
let rc = adjust_window_rect(
size,
WINDOW_STYLE(GetWindowLongPtrW(hwnd, GWL_STYLE) as _),
WINDOW_EX_STYLE(GetWindowLongPtrW(hwnd, GWL_EXSTYLE) as _),
next_dpi as u32,
);
let mut ret = (lparam.0 as *mut SIZE).as_mut().unwrap();
ret.cx = rc.right - rc.left;
ret.cy = rc.bottom - rc.top;
LRESULT(1)
}
WM_DROPFILES => {
let hdrop = HDROP(wparam.0 as _);
let file_count = DragQueryFileW(hdrop, std::u32::MAX, &mut []);
let mut buffer = Vec::new();
let files = (0..file_count)
.map(|i| {
let len = DragQueryFileW(hdrop, i, &mut []) as usize + 1;
buffer.resize(len, 0);
DragQueryFileW(hdrop, i, &mut buffer);
buffer.pop();
PathBuf::from(String::from_utf16_lossy(&buffer))
})
.collect::<Vec<_>>();
let files_ref = files.iter().map(|pb| pb.as_path()).collect::<Vec<_>>();
let mut pt = POINT::default();
DragQueryPoint(hdrop, &mut pt);
call_handler(|eh: &mut T, _| {
eh.drop_files(event::DropFiles {
window: handle,
paths: &files_ref,
position: PhysicalPosition::new(pt.x, pt.y),
});
});
DragFinish(hdrop);
LRESULT(0)
}
#[cfg(feature = "raw_input")]
WM_INPUT_DEVICE_CHANGE => {
raw_input::wm_input_device_change::<T>(handle, hwnd, wparam, lparam)
}
WM_DESTROY => {
{
let mut state = handle.state.write().unwrap();
state.closed = true;
}
call_handler(|eh: &mut T, _| {
eh.closed(event::Closed { window: handle });
{
let state = handle.state.read().unwrap();
for child in state.children.iter() {
child.close();
}
}
});
remove_window(hwnd);
if window_table_is_empty() {
PostQuitMessage(0);
}
LRESULT(0)
}
WM_NCCREATE => {
EnableNonClientDpiScaling(hwnd);
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_USER => {
match wparam.0 {
w if w == UserMessage::SetTitle as usize => {
let state = handle.state.read().unwrap();
SetWindowTextW(hwnd, &HSTRING::from(&state.title));
}
w if w == UserMessage::SetPosition as usize => {
let state = handle.state.read().unwrap();
SetWindowPos(
hwnd,
HWND(0),
state.set_position.0,
state.set_position.1,
0,
0,
SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
);
}
w if w == UserMessage::SetInnerSize as usize => {
let state = handle.state.read().unwrap();
let rc = adjust_window_rect(
state.set_inner_size,
WINDOW_STYLE(GetWindowLongPtrW(hwnd, GWL_STYLE) as _),
WINDOW_EX_STYLE(GetWindowLongPtrW(hwnd, GWL_EXSTYLE) as _),
GetDpiForWindow(hwnd),
);
SetWindowPos(
hwnd,
HWND(0),
0,
0,
rc.right - rc.left,
rc.bottom - rc.top,
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE,
);
}
w if w == UserMessage::EnableIme as usize => {
window.ime_context.borrow().enable();
}
w if w == UserMessage::DisableIme as usize => {
window.ime_context.borrow().disable();
}
w if w == UserMessage::SetStyle as usize => {
let style = {
let state = handle.state.read().unwrap();
state.style
};
let rc = adjust_window_rect(
handle.inner_size().to_physical(handle.dpi()),
style,
WINDOW_EX_STYLE(0),
GetDpiForWindow(hwnd),
);
SetWindowLongPtrW(hwnd, GWL_STYLE, style.0 as _);
SetWindowPos(
hwnd,
HWND(0),
0,
0,
rc.right - rc.left,
rc.bottom - rc.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED,
);
ShowWindow(hwnd, SW_SHOW);
}
w if w == UserMessage::AcceptDragFiles as usize => {
DragAcceptFiles(hwnd, BOOL(lparam.0 as _));
}
_ => {
return call_other::<T>(hwnd, msg, wparam, lparam);
}
}
LRESULT(0)
}
_ => call_other::<T>(hwnd, msg, wparam, lparam),
}
});
ret.unwrap_or_else(|e| {
set_unwind(e);
LRESULT(0)
})
}