use crate::*;
use std::collections::{BTreeSet, HashMap};
use std::convert::TryInto;
use std::ffi::OsStr;
use std::mem;
use std::os::raw::c_void;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use std::sync::Mutex;
use time::precise_time_ns;
use winapi::shared::basetsd::UINT_PTR;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::ntdef::NULL;
use winapi::shared::windef::{DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, HMONITOR, HWND, RECT};
use winapi::shared::winerror::S_OK;
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
use winapi::um::shellscalingapi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, PROCESS_PER_MONITOR_DPI_AWARE};
use winapi::um::uxtheme::MARGINS;
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
use winapi::um::winnt::{HRESULT, LPCSTR, LPCWSTR};
use winapi::um::winuser::{MONITOR_DEFAULTTONEAREST, TRACKMOUSEEVENT};
use winapi::um::{dwmapi, libloaderapi, winbase, winuser};
static mut GLOBAL_WIN32_APP: *mut Win32App = 0 as *mut _;
pub(crate) struct Win32App {
pub(crate) time_start: u64,
pub(crate) event_callback: Option<*mut dyn FnMut(&mut Win32App, &mut Vec<Event>) -> bool>,
pub(crate) event_recur_block: bool,
pub(crate) event_loop_running: bool,
pub(crate) class_name_wstr: Vec<u16>,
pub(crate) all_windows: Vec<HWND>,
pub(crate) timers: Vec<Win32Timer>,
pub(crate) free_timers: Vec<usize>,
pub(crate) race_signals: Mutex<Vec<(usize, isize)>>,
pub(crate) loop_block: bool,
pub(crate) dpi_functions: DpiFunctions,
pub(crate) current_cursor: MouseCursor,
}
#[derive(Clone)]
pub(crate) struct Win32Window {
pub(crate) window_id: usize,
pub(crate) win32_app: *mut Win32App,
pub(crate) last_window_geom: WindowGeom,
pub(crate) time_start: u64,
pub(crate) ime_spot: Vec2,
pub(crate) last_mouse_pos: Vec2,
pub(crate) pointers_down: Vec<bool>,
pub(crate) hwnd: Option<HWND>,
pub(crate) track_mouse_event: bool,
}
#[derive(Clone)]
pub(crate) enum Win32Timer {
Free,
Timer { win32_id: UINT_PTR, timer_id: u64, repeats: bool },
Resize { win32_id: UINT_PTR },
}
impl Win32App {
pub(crate) fn new() -> Win32App {
let class_name_wstr: Vec<u16> = OsStr::new("ZapWindow").encode_wide().chain(Some(0).into_iter()).collect();
let class = winuser::WNDCLASSEXW {
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC,
lpfnWndProc: Some(Win32Window::window_class_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: unsafe { libloaderapi::GetModuleHandleW(ptr::null()) },
hIcon: unsafe { winuser::LoadIconW(ptr::null_mut(), winuser::IDI_WINLOGO) }, hCursor: ptr::null_mut(), hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name_wstr.as_ptr(),
hIconSm: ptr::null_mut(),
};
unsafe {
winuser::RegisterClassExW(&class);
winuser::IsGUIThread(1);
}
let win32_app = Win32App {
class_name_wstr,
time_start: precise_time_ns(),
race_signals: Mutex::new(Vec::new()),
event_callback: None,
event_recur_block: false,
event_loop_running: true,
loop_block: false,
all_windows: Vec::new(),
timers: Vec::new(),
free_timers: Vec::new(),
dpi_functions: DpiFunctions::new(),
current_cursor: MouseCursor::Default,
};
win32_app.dpi_functions.become_dpi_aware();
win32_app
}
pub(crate) fn init(&mut self) {
unsafe {
GLOBAL_WIN32_APP = self;
}
}
pub(crate) fn event_loop<F>(&mut self, mut event_handler: F)
where
F: FnMut(&mut Win32App, &mut Vec<Event>) -> bool,
{
unsafe {
self.event_callback = Some(
&mut event_handler as *const dyn FnMut(&mut Win32App, &mut Vec<Event>) -> bool
as *mut dyn FnMut(&mut Win32App, &mut Vec<Event>) -> bool,
);
while self.event_loop_running {
if self.loop_block {
let mut msg = std::mem::MaybeUninit::uninit();
let ret = winuser::GetMessageW(msg.as_mut_ptr(), ptr::null_mut(), 0, 0);
let msg = msg.assume_init();
if ret == 0 {
debug_assert_eq!(msg.message, winuser::WM_QUIT);
self.event_loop_running = false;
} else {
winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg);
self.do_callback(&mut vec![Event::SystemEvent(SystemEvent::Paint)]);
}
} else {
let mut msg = std::mem::MaybeUninit::uninit();
let ret = winuser::PeekMessageW(msg.as_mut_ptr(), ptr::null_mut(), 0, 0, 1);
let msg = msg.assume_init();
if ret == 0 {
self.do_callback(&mut vec![Event::SystemEvent(SystemEvent::Paint)])
} else {
winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg);
}
}
}
self.event_callback = None;
}
}
pub(crate) fn do_callback(&mut self, events: &mut Vec<Event>) {
unsafe {
if self.event_callback.is_none() || self.event_recur_block {
return;
};
self.event_recur_block = true;
let callback = self.event_callback.unwrap();
self.loop_block = (*callback)(self, events);
self.event_recur_block = false;
}
}
pub(crate) unsafe extern "system" fn timer_proc(_hwnd: HWND, _arg1: UINT, in_win32_id: UINT_PTR, _arg2: DWORD) {
let win32_app = &mut (*GLOBAL_WIN32_APP);
let hit_timer = {
let mut hit_timer = None;
for slot in 0..win32_app.timers.len() {
match win32_app.timers[slot] {
Win32Timer::Timer { win32_id, repeats, .. } => {
if win32_id == in_win32_id {
hit_timer = Some(win32_app.timers[slot].clone());
if !repeats {
winuser::KillTimer(NULL as HWND, in_win32_id);
win32_app.timers[slot] = Win32Timer::Free;
win32_app.free_timers.push(slot);
}
break;
}
}
Win32Timer::Resize { win32_id, .. } => {
if win32_id == in_win32_id {
hit_timer = Some(win32_app.timers[slot].clone());
break;
}
}
_ => (),
}
}
hit_timer
};
if let Some(hit_timer) = hit_timer {
match hit_timer {
Win32Timer::Timer { timer_id, .. } => {
win32_app.do_callback(&mut vec![Event::Timer(TimerEvent { timer_id })]);
}
Win32Timer::Resize { .. } => {
win32_app.do_callback(&mut vec![Event::SystemEvent(SystemEvent::Paint)]);
}
_ => (),
}
}
}
pub(crate) fn get_free_timer_slot(&mut self) -> usize {
if !self.free_timers.is_empty() {
self.free_timers.pop().unwrap()
} else {
let slot = self.timers.len();
self.timers.push(Win32Timer::Free);
slot
}
}
pub(crate) fn start_timer(&mut self, timer_id: u64, interval: f64, repeats: bool) {
let slot = self.get_free_timer_slot();
let win32_id = unsafe { winuser::SetTimer(NULL as HWND, 0, (interval * 1000.0) as u32, Some(Self::timer_proc)) };
self.timers[slot] = Win32Timer::Timer { timer_id, win32_id, repeats };
}
pub(crate) fn stop_timer(&mut self, which_timer_id: u64) {
for slot in 0..self.timers.len() {
if let Win32Timer::Timer { win32_id, timer_id, .. } = self.timers[slot] {
if timer_id == which_timer_id {
self.timers[slot] = Win32Timer::Free;
self.free_timers.push(slot);
unsafe {
winuser::KillTimer(NULL as HWND, win32_id);
}
}
}
}
}
pub(crate) fn start_resize(&mut self) {
let slot = self.get_free_timer_slot();
let win32_id = unsafe { winuser::SetTimer(NULL as HWND, 0, 8_u32, Some(Self::timer_proc)) };
self.timers[slot] = Win32Timer::Resize { win32_id };
}
pub(crate) fn stop_resize(&mut self) {
for slot in 0..self.timers.len() {
if let Win32Timer::Resize { win32_id } = self.timers[slot] {
self.timers[slot] = Win32Timer::Free;
self.free_timers.push(slot);
unsafe {
winuser::KillTimer(NULL as HWND, win32_id);
}
}
}
}
pub(crate) fn post_signal(signal: Signal, status: StatusId) {
unsafe {
let win32_app = &mut (*GLOBAL_WIN32_APP);
let status_id = isize::from_ne_bytes(status.0.to_ne_bytes());
if let Ok(mut sigs) = win32_app.race_signals.lock() {
if !win32_app.all_windows.is_empty() {
winuser::PostMessageW(
win32_app.all_windows[0],
winuser::WM_USER,
signal.signal_id as usize,
status_id as isize,
);
} else {
sigs.push((signal.signal_id as usize, status_id as isize));
}
}
}
}
pub(crate) fn terminate_event_loop(&mut self) {
unsafe {
if !self.all_windows.is_empty() {
winuser::PostMessageW(self.all_windows[0], winuser::WM_QUIT, 0, 0);
}
}
self.event_loop_running = false;
}
pub(crate) fn time_now(&self) -> f64 {
let time_now = precise_time_ns();
(time_now - self.time_start) as f64 / 1_000_000_000.0
}
pub(crate) fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
if self.current_cursor != cursor {
let win32_cursor = match cursor {
MouseCursor::Hidden => ptr::null(),
MouseCursor::Default => winuser::IDC_ARROW,
MouseCursor::Crosshair => winuser::IDC_CROSS,
MouseCursor::Hand => winuser::IDC_HAND,
MouseCursor::Arrow => winuser::IDC_ARROW,
MouseCursor::Move => winuser::IDC_SIZEALL,
MouseCursor::Text => winuser::IDC_IBEAM,
MouseCursor::Wait => winuser::IDC_ARROW,
MouseCursor::Help => winuser::IDC_HELP,
MouseCursor::NotAllowed => winuser::IDC_NO,
MouseCursor::EResize => winuser::IDC_SIZEWE,
MouseCursor::NResize => winuser::IDC_SIZENS,
MouseCursor::NeResize => winuser::IDC_SIZENESW,
MouseCursor::NwResize => winuser::IDC_SIZENWSE,
MouseCursor::SResize => winuser::IDC_SIZENS,
MouseCursor::SeResize => winuser::IDC_SIZENWSE,
MouseCursor::SwResize => winuser::IDC_SIZENESW,
MouseCursor::WResize => winuser::IDC_SIZEWE,
MouseCursor::NsResize => winuser::IDC_SIZENS,
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
MouseCursor::EwResize => winuser::IDC_SIZEWE,
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
MouseCursor::ColResize => winuser::IDC_SIZEWE,
MouseCursor::RowResize => winuser::IDC_SIZENS,
};
self.current_cursor = cursor;
unsafe {
if win32_cursor == ptr::null() {
winuser::ShowCursor(0);
} else {
winuser::SetCursor(winuser::LoadCursorW(ptr::null_mut(), win32_cursor));
winuser::ShowCursor(1);
}
}
}
}
pub(crate) fn copy_text_to_clipboard(text: &str) {
unsafe {
if winuser::OpenClipboard(ptr::null_mut()) != 0 {
winuser::EmptyClipboard();
let data: Vec<u16> = OsStr::new(text).encode_wide().chain(Some(0).into_iter()).collect();
let h_clipboard_data = winbase::GlobalAlloc(winbase::GMEM_DDESHARE, 2 * data.len());
let h_clipboard_ptr = winbase::GlobalLock(h_clipboard_data) as *mut u16;
std::ptr::copy_nonoverlapping(data.as_ptr(), h_clipboard_ptr, data.len());
winbase::GlobalUnlock(h_clipboard_data);
winuser::SetClipboardData(winuser::CF_UNICODETEXT, h_clipboard_data);
winuser::CloseClipboard();
}
}
}
}
impl Win32Window {
pub(crate) fn new(win32_app: &mut Win32App, window_id: usize) -> Win32Window {
let mut pointers_down = Vec::new();
pointers_down.resize(NUM_POINTERS, false);
Win32Window {
window_id,
win32_app,
last_window_geom: WindowGeom::default(),
time_start: win32_app.time_start,
ime_spot: Vec2::default(),
last_mouse_pos: Vec2::default(),
pointers_down,
hwnd: None,
track_mouse_event: false,
}
}
pub(crate) fn init(&mut self, title: &str, size: Vec2, position: Option<Vec2>) {
let style = winuser::WS_SIZEBOX
| winuser::WS_MAXIMIZEBOX
| winuser::WS_MINIMIZEBOX
| winuser::WS_POPUP
| winuser::WS_CLIPSIBLINGS
| winuser::WS_CLIPCHILDREN
| winuser::WS_SYSMENU;
let style_ex = winuser::WS_EX_WINDOWEDGE | winuser::WS_EX_APPWINDOW | winuser::WS_EX_ACCEPTFILES;
unsafe {
let title_wstr: Vec<_> = OsStr::new(title).encode_wide().chain(Some(0).into_iter()).collect();
let (x, y) = if let Some(position) = position {
(position.x as i32, position.y as i32)
} else {
(winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT)
};
let hwnd = winuser::CreateWindowExW(
style_ex,
(*self.win32_app).class_name_wstr.as_ptr(),
title_wstr.as_ptr() as LPCWSTR,
style,
x,
y,
winuser::CW_USEDEFAULT,
winuser::CW_USEDEFAULT,
ptr::null_mut(),
ptr::null_mut(),
libloaderapi::GetModuleHandleW(ptr::null()),
ptr::null_mut(),
);
self.hwnd = Some(hwnd);
winuser::SetWindowLongPtrW(hwnd, winuser::GWLP_USERDATA, self as *const _ as isize);
self.set_outer_size(size);
winuser::ShowWindow(hwnd, winuser::SW_SHOW);
(*self.win32_app).dpi_functions.enable_non_client_dpi_scaling(self.hwnd.unwrap());
if let Ok(mut sigs) = (*self.win32_app).race_signals.lock() {
(*self.win32_app).all_windows.push(hwnd);
for sig in sigs.iter() {
winuser::PostMessageW(hwnd, winuser::WM_USER, sig.0, sig.1 as isize);
}
sigs.truncate(0);
}
}
}
pub(crate) unsafe extern "system" fn window_class_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let user_data = winuser::GetWindowLongPtrW(hwnd, winuser::GWLP_USERDATA);
if user_data == 0 {
return winuser::DefWindowProcW(hwnd, msg, wparam, lparam);
};
let window = &mut (*(user_data as *mut Win32Window));
match msg {
winuser::WM_ACTIVATE => {
if wparam & 0xffff == winuser::WA_ACTIVE as usize {
window.do_callback(&mut vec![Event::AppFocus]);
} else {
window.do_callback(&mut vec![Event::AppFocusLost]);
}
}
winuser::WM_NCCALCSIZE => {
if window.get_is_maximized() {
return winuser::DefWindowProcW(hwnd, msg, wparam, lparam);
}
if wparam == 1 {
let margins = MARGINS { cxLeftWidth: 0, cxRightWidth: 0, cyTopHeight: 0, cyBottomHeight: 1 };
dwmapi::DwmExtendFrameIntoClientArea(hwnd, &margins);
return 0;
}
}
winuser::WM_NCHITTEST => {
let ycoord = (lparam >> 16) as u16 as i16 as i32;
let xcoord = (lparam & 0xffff) as u16 as i16 as i32;
let mut rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
const EDGE: i32 = 8;
winuser::GetWindowRect(hwnd, &mut rect);
if xcoord < rect.left + EDGE {
(*window.win32_app).current_cursor = MouseCursor::Hidden;
if ycoord < rect.top + EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::NwseResize,
))]);
return winuser::HTTOPLEFT;
}
if ycoord > rect.bottom - EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::NeswResize,
))]);
return winuser::HTBOTTOMLEFT;
}
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(MouseCursor::EwResize))]);
return winuser::HTLEFT;
}
if xcoord > rect.right - EDGE {
(*window.win32_app).current_cursor = MouseCursor::Hidden;
if ycoord < rect.top + EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::NeswResize,
))]);
return winuser::HTTOPRIGHT;
}
if ycoord > rect.bottom - EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::NwseResize,
))]);
return winuser::HTBOTTOMRIGHT;
}
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(MouseCursor::EwResize))]);
return winuser::HTRIGHT;
}
if ycoord < rect.top + EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(MouseCursor::NsResize))]);
return winuser::HTTOP;
}
if ycoord > rect.bottom - EDGE {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(MouseCursor::NsResize))]);
return winuser::HTBOTTOM;
}
let mut events = vec![Event::WindowDragQuery(WindowDragQueryEvent {
window_id: window.window_id,
abs: window.get_mouse_pos_from_lparam(lparam),
response: WindowDragQueryResponse::NoAnswer,
})];
window.do_callback(&mut events);
match &events[0] {
Event::WindowDragQuery(wd) => match &wd.response {
WindowDragQueryResponse::Client => return winuser::HTCLIENT,
WindowDragQueryResponse::Caption => {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::Default,
))]);
return winuser::HTCAPTION;
}
WindowDragQueryResponse::SysMenu => {
window.do_callback(&mut vec![Event::SystemEvent(SystemEvent::WindowSetHoverCursor(
MouseCursor::Default,
))]);
return winuser::HTSYSMENU;
}
_ => (),
},
_ => (),
}
if ycoord < rect.top + 50 && xcoord < rect.left + 50 {
return winuser::HTSYSMENU;
}
if ycoord < rect.top + 50 && xcoord < rect.right - 300 {
return winuser::HTCAPTION;
}
return winuser::HTCLIENT;
}
winuser::WM_ERASEBKGND => return 1,
winuser::WM_MOUSEMOVE => {
if !window.track_mouse_event {
window.track_mouse_event = true;
let mut tme = TRACKMOUSEEVENT {
cbSize: mem::size_of::<TRACKMOUSEEVENT>() as u32,
dwFlags: winuser::TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: 0,
};
winuser::TrackMouseEvent(&mut tme);
}
window.send_pointer_hover_and_move(window.get_mouse_pos_from_lparam(lparam), Self::get_key_modifiers())
}
winuser::WM_MOUSELEAVE => {
window.track_mouse_event = false;
window.do_callback(&mut vec![Event::PointerHover(PointerHoverEvent {
digit: 0,
window_id: window.window_id,
any_down: false,
abs: window.last_mouse_pos,
rel: window.last_mouse_pos,
rect: Rect::default(),
handled: false,
hover_state: HoverState::Out,
modifiers: Self::get_key_modifiers(),
time: window.time_now(),
})]);
(*window.win32_app).current_cursor = MouseCursor::Hidden;
}
winuser::WM_MOUSEWHEEL => {
let delta = (wparam >> 16) as u16 as i16 as f32;
window.do_callback(&mut vec![Event::PointerScroll(PointerScrollEvent {
digit: 0,
window_id: window.window_id,
scroll: Vec2 { x: 0.0, y: -delta },
abs: window.last_mouse_pos,
rel: window.last_mouse_pos,
rect: Rect::default(),
input_type: PointerInputType::Mouse,
modifiers: Self::get_key_modifiers(),
handled_x: false,
handled_y: false,
time: window.time_now(),
})]);
}
winuser::WM_LBUTTONDOWN => window.send_pointer_down(0, MouseButton::Left, Self::get_key_modifiers()),
winuser::WM_LBUTTONUP => window.send_pointer_up(0, MouseButton::Left, Self::get_key_modifiers()),
winuser::WM_RBUTTONDOWN => window.send_pointer_down(1, MouseButton::Right, Self::get_key_modifiers()),
winuser::WM_RBUTTONUP => window.send_pointer_up(1, MouseButton::Right, Self::get_key_modifiers()),
winuser::WM_MBUTTONDOWN => window.send_pointer_down(2, MouseButton::Other, Self::get_key_modifiers()),
winuser::WM_MBUTTONUP => window.send_pointer_up(2, MouseButton::Other, Self::get_key_modifiers()),
winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => {
let modifiers = Self::get_key_modifiers();
let key_code = Self::virtual_key_to_key_code(wparam);
if modifiers.alt && key_code == KeyCode::F4 {
winuser::PostMessageW(hwnd, winuser::WM_CLOSE, 0, 0);
}
if modifiers.control || modifiers.logo {
match key_code {
KeyCode::KeyV => {
if winuser::OpenClipboard(ptr::null_mut()) != 0 {
let mut data: Vec<u16> = Vec::new();
let h_clipboard_data = winuser::GetClipboardData(winuser::CF_UNICODETEXT);
let h_clipboard_ptr = winbase::GlobalLock(h_clipboard_data) as *mut u16;
let clipboard_size = winbase::GlobalSize(h_clipboard_data);
if clipboard_size > 2 {
data.resize((clipboard_size >> 1) - 1, 0);
std::ptr::copy_nonoverlapping(h_clipboard_ptr, data.as_mut_ptr(), data.len());
winbase::GlobalUnlock(h_clipboard_data);
winuser::CloseClipboard();
if let Ok(utf8) = String::from_utf16(&data) {
window.do_callback(&mut vec![Event::TextInput(TextInputEvent {
input: utf8,
was_paste: true,
replace_last: false,
})]);
}
} else {
winbase::GlobalUnlock(h_clipboard_data);
winuser::CloseClipboard();
}
}
}
KeyCode::KeyX | KeyCode::KeyC => {
let mut events = vec![Event::TextCopy];
window.do_callback(&mut events);
}
_ => (),
}
}
window.do_callback(&mut vec![Event::KeyDown(KeyEvent {
key_code,
is_repeat: lparam & 0x7fff > 0,
modifiers,
time: window.time_now(),
})]);
}
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
window.do_callback(&mut vec![Event::KeyUp(KeyEvent {
key_code: Self::virtual_key_to_key_code(wparam),
is_repeat: lparam & 0x7fff > 0,
modifiers: Self::get_key_modifiers(),
time: window.time_now(),
})]);
}
winuser::WM_CHAR => {
if let Ok(utf8) = String::from_utf16(&[wparam as u16]) {
let char_code = utf8.chars().next().unwrap();
if char_code >= ' ' {
window.do_callback(&mut vec![Event::TextInput(TextInputEvent {
input: utf8,
was_paste: false,
replace_last: false,
})]);
}
}
}
winuser::WM_ENTERSIZEMOVE => {
(*window.win32_app).start_resize();
window.do_callback(&mut vec![Event::WindowResizeLoop(WindowResizeLoopEvent {
was_started: true,
window_id: window.window_id,
})]);
}
winuser::WM_EXITSIZEMOVE => {
(*window.win32_app).stop_resize();
window.do_callback(&mut vec![Event::WindowResizeLoop(WindowResizeLoopEvent {
was_started: false,
window_id: window.window_id,
})]);
}
winuser::WM_SIZE | winuser::WM_DPICHANGED => {
window.send_change_event();
}
winuser::WM_USER => {
let status_id = usize::from_ne_bytes(lparam.to_ne_bytes());
let status = LocationHash(status_id.try_into().unwrap());
let mut signals = HashMap::new();
let mut set = BTreeSet::new();
set.insert(status);
signals.insert(Signal { signal_id: wparam as usize }, set);
window.do_callback(&mut vec![Event::Signal(SignalEvent { signals })]);
}
winuser::WM_CLOSE => {
let mut events = vec![Event::WindowCloseRequested(WindowCloseRequestedEvent {
window_id: window.window_id,
accept_close: true,
})];
window.do_callback(&mut events);
if let Event::WindowCloseRequested(cre) = &events[0] {
if cre.accept_close {
winuser::DestroyWindow(hwnd);
}
}
}
winuser::WM_DESTROY => {
(*window.win32_app).event_recur_block = false; window.do_callback(&mut vec![Event::WindowClosed(WindowClosedEvent { window_id: window.window_id })]);
}
_ => return winuser::DefWindowProcW(hwnd, msg, wparam, lparam),
}
1
}
pub(crate) fn get_mouse_pos_from_lparam(&self, lparam: LPARAM) -> Vec2 {
let dpi = self.get_dpi_factor();
let ycoord = (lparam >> 16) as u16 as i16 as f32;
let xcoord = (lparam & 0xffff) as u16 as i16 as f32;
Vec2 { x: xcoord / dpi, y: ycoord / dpi }
}
pub(crate) fn get_key_modifiers() -> KeyModifiers {
unsafe {
KeyModifiers {
control: winuser::GetKeyState(winuser::VK_CONTROL) & 0x80 > 0,
shift: winuser::GetKeyState(winuser::VK_SHIFT) & 0x80 > 0,
alt: winuser::GetKeyState(winuser::VK_MENU) & 0x80 > 0,
logo: winuser::GetKeyState(winuser::VK_LWIN) & 0x80 > 0 || winuser::GetKeyState(winuser::VK_RWIN) & 0x80 > 0,
}
}
}
pub(crate) fn update_ptrs(&mut self) {
unsafe {
winuser::SetWindowLongPtrW(self.hwnd.unwrap(), winuser::GWLP_USERDATA, self as *const _ as isize);
}
}
pub(crate) fn restore(&self) {
unsafe {
winuser::ShowWindow(self.hwnd.unwrap(), winuser::SW_RESTORE);
winuser::PostMessageW(self.hwnd.unwrap(), winuser::WM_SIZE, 0, 0);
}
}
pub(crate) fn maximize(&self) {
unsafe {
winuser::ShowWindow(self.hwnd.unwrap(), winuser::SW_MAXIMIZE);
winuser::PostMessageW(self.hwnd.unwrap(), winuser::WM_SIZE, 0, 0);
}
}
pub(crate) fn close_window(&self) {
unsafe {
winuser::DestroyWindow(self.hwnd.unwrap());
}
}
pub(crate) fn minimize(&self) {
unsafe {
winuser::ShowWindow(self.hwnd.unwrap(), winuser::SW_MINIMIZE);
}
}
pub(crate) fn set_topmost(&self, topmost: bool) {
unsafe {
if topmost {
winuser::SetWindowPos(
self.hwnd.unwrap(),
winuser::HWND_TOPMOST,
0,
0,
0,
0,
winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
} else {
winuser::SetWindowPos(
self.hwnd.unwrap(),
winuser::HWND_NOTOPMOST,
0,
0,
0,
0,
winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
}
}
}
pub(crate) fn get_is_topmost(&self) -> bool {
unsafe {
let ex_style = winuser::GetWindowLongW(self.hwnd.unwrap(), winuser::GWL_EXSTYLE) as u32;
if (ex_style & winuser::WS_EX_TOPMOST) != 0 {
return true;
}
false
}
}
pub(crate) fn get_window_geom(&self) -> WindowGeom {
WindowGeom {
xr_can_present: false,
xr_is_presenting: false,
can_fullscreen: false,
is_topmost: self.get_is_topmost(),
is_fullscreen: self.get_is_maximized(),
inner_size: self.get_inner_size(),
outer_size: self.get_outer_size(),
dpi_factor: self.get_dpi_factor(),
position: self.get_position(),
}
}
pub(crate) fn get_is_maximized(&self) -> bool {
unsafe {
let wp: mem::MaybeUninit<winuser::WINDOWPLACEMENT> = mem::MaybeUninit::uninit();
let mut wp = wp.assume_init();
wp.length = mem::size_of::<winuser::WINDOWPLACEMENT>() as u32;
winuser::GetWindowPlacement(self.hwnd.unwrap(), &mut wp);
if wp.showCmd as i32 == winuser::SW_MAXIMIZE {
return true;
}
false
}
}
pub(crate) fn time_now(&self) -> f64 {
let time_now = precise_time_ns();
(time_now - self.time_start) as f64 / 1_000_000_000.0
}
pub(crate) fn set_ime_spot(&mut self, spot: Vec2) {
self.ime_spot = spot;
}
pub(crate) fn get_position(&self) -> Vec2 {
unsafe {
let mut rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
winuser::GetWindowRect(self.hwnd.unwrap(), &mut rect);
Vec2 { x: rect.left as f32, y: rect.top as f32 }
}
}
pub(crate) fn get_inner_size(&self) -> Vec2 {
unsafe {
let mut rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
winuser::GetClientRect(self.hwnd.unwrap(), &mut rect);
let dpi = self.get_dpi_factor();
Vec2 { x: (rect.right - rect.left) as f32 / dpi, y: (rect.bottom - rect.top) as f32 / dpi }
}
}
pub(crate) fn get_outer_size(&self) -> Vec2 {
unsafe {
let mut rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
winuser::GetWindowRect(self.hwnd.unwrap(), &mut rect);
Vec2 { x: (rect.right - rect.left) as f32, y: (rect.bottom - rect.top) as f32 }
}
}
pub(crate) fn set_position(&mut self, pos: Vec2) {
unsafe {
let mut window_rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
winuser::GetWindowRect(self.hwnd.unwrap(), &mut window_rect);
let dpi = self.get_dpi_factor();
winuser::MoveWindow(
self.hwnd.unwrap(),
(pos.x * dpi) as i32,
(pos.y * dpi) as i32,
window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top,
FALSE,
);
}
}
pub(crate) fn set_outer_size(&self, size: Vec2) {
unsafe {
let mut window_rect = RECT { left: 0, top: 0, bottom: 0, right: 0 };
winuser::GetWindowRect(self.hwnd.unwrap(), &mut window_rect);
let dpi = self.get_dpi_factor();
winuser::MoveWindow(
self.hwnd.unwrap(),
window_rect.left,
window_rect.top,
(size.x * dpi) as i32,
(size.y * dpi) as i32,
FALSE,
);
}
}
pub(crate) fn get_dpi_factor(&self) -> f32 {
unsafe { (*self.win32_app).dpi_functions.hwnd_dpi_factor(self.hwnd.unwrap()) }
}
pub(crate) fn do_callback(&mut self, events: &mut Vec<Event>) {
unsafe {
(*self.win32_app).do_callback(events);
}
}
pub(crate) fn send_change_event(&mut self) {
let new_geom = self.get_window_geom();
let old_geom = self.last_window_geom.clone();
self.last_window_geom = new_geom.clone();
self.do_callback(&mut vec![
Event::WindowGeomChange(WindowGeomChangeEvent { window_id: self.window_id, old_geom, new_geom }),
Event::SystemEvent(SystemEvent::Paint),
]);
}
pub(crate) fn send_pointer_down(&mut self, digit: usize, button: MouseButton, modifiers: KeyModifiers) {
let mut down_count = 0;
for is_down in &self.pointers_down {
if *is_down {
down_count += 1;
}
}
if down_count == 0 {
unsafe {
winuser::SetCapture(self.hwnd.unwrap());
}
}
self.pointers_down[digit] = true;
self.do_callback(&mut vec![Event::PointerDown(PointerDownEvent {
window_id: self.window_id,
abs: self.last_mouse_pos,
rel: self.last_mouse_pos,
rect: Rect::default(),
digit,
button,
handled: false,
input_type: PointerInputType::Mouse,
modifiers,
tap_count: 0,
time: self.time_now(),
})]);
}
pub(crate) fn send_pointer_up(&mut self, digit: usize, button: MouseButton, modifiers: KeyModifiers) {
self.pointers_down[digit] = false;
let mut down_count = 0;
for is_down in &self.pointers_down {
if *is_down {
down_count += 1;
}
}
if down_count == 0 {
unsafe {
winuser::ReleaseCapture();
}
}
self.do_callback(&mut vec![Event::PointerUp(PointerUpEvent {
window_id: self.window_id,
abs: self.last_mouse_pos,
rel: self.last_mouse_pos,
rect: Rect::default(),
abs_start: Vec2::default(),
rel_start: Vec2::default(),
digit,
button,
is_over: false,
input_type: PointerInputType::Mouse,
modifiers,
time: self.time_now(),
})]);
}
pub(crate) fn send_pointer_hover_and_move(&mut self, pos: Vec2, modifiers: KeyModifiers) {
self.last_mouse_pos = pos;
let mut events = Vec::new();
for (digit, down) in self.pointers_down.iter().enumerate() {
if *down {
events.push(Event::PointerMove(PointerMoveEvent {
window_id: self.window_id,
abs: pos,
rel: pos,
rect: Rect::default(),
digit,
abs_start: Vec2::default(),
rel_start: Vec2::default(),
is_over: false,
input_type: PointerInputType::Mouse,
modifiers: modifiers.clone(),
time: self.time_now(),
}));
}
}
events.push(Event::PointerHover(PointerHoverEvent {
digit: 0,
window_id: self.window_id,
abs: pos,
rel: pos,
any_down: false,
rect: Rect::default(),
handled: false,
hover_state: HoverState::Over,
modifiers,
time: self.time_now(),
}));
self.do_callback(&mut events);
}
pub(crate) fn virtual_key_to_key_code(wparam: WPARAM) -> KeyCode {
match wparam as i32 {
winuser::VK_ESCAPE => KeyCode::Escape,
winuser::VK_OEM_3 => KeyCode::Backtick,
0x30 => KeyCode::Key0,
0x31 => KeyCode::Key1,
0x32 => KeyCode::Key2,
0x33 => KeyCode::Key3,
0x34 => KeyCode::Key4,
0x35 => KeyCode::Key5,
0x36 => KeyCode::Key6,
0x37 => KeyCode::Key7,
0x38 => KeyCode::Key8,
0x39 => KeyCode::Key9,
winuser::VK_OEM_MINUS => KeyCode::Minus,
winuser::VK_OEM_PLUS => KeyCode::Equals,
winuser::VK_BACK => KeyCode::Backspace,
winuser::VK_TAB => KeyCode::Tab,
0x51 => KeyCode::KeyQ,
0x57 => KeyCode::KeyW,
0x45 => KeyCode::KeyE,
0x52 => KeyCode::KeyR,
0x54 => KeyCode::KeyT,
0x59 => KeyCode::KeyY,
0x55 => KeyCode::KeyU,
0x49 => KeyCode::KeyI,
0x4f => KeyCode::KeyO,
0x50 => KeyCode::KeyP,
winuser::VK_OEM_4 => KeyCode::LBracket,
winuser::VK_OEM_6 => KeyCode::RBracket,
winuser::VK_RETURN => KeyCode::Return,
0x41 => KeyCode::KeyA,
0x53 => KeyCode::KeyS,
0x44 => KeyCode::KeyD,
0x46 => KeyCode::KeyF,
0x47 => KeyCode::KeyG,
0x48 => KeyCode::KeyH,
0x4a => KeyCode::KeyJ,
0x4b => KeyCode::KeyK,
0x4c => KeyCode::KeyL,
winuser::VK_OEM_1 => KeyCode::Semicolon,
winuser::VK_OEM_7 => KeyCode::Quote,
winuser::VK_OEM_5 => KeyCode::Backslash,
0x5a => KeyCode::KeyZ,
0x58 => KeyCode::KeyX,
0x43 => KeyCode::KeyC,
0x56 => KeyCode::KeyV,
0x42 => KeyCode::KeyB,
0x4e => KeyCode::KeyN,
0x4d => KeyCode::KeyM,
winuser::VK_OEM_COMMA => KeyCode::Comma,
winuser::VK_OEM_PERIOD => KeyCode::Period,
winuser::VK_OEM_2 => KeyCode::Slash,
winuser::VK_LCONTROL => KeyCode::Control,
winuser::VK_RCONTROL => KeyCode::Control,
winuser::VK_CONTROL => KeyCode::Control,
winuser::VK_LMENU => KeyCode::Alt,
winuser::VK_RMENU => KeyCode::Alt,
winuser::VK_MENU => KeyCode::Alt,
winuser::VK_LSHIFT => KeyCode::Shift,
winuser::VK_RSHIFT => KeyCode::Shift,
winuser::VK_SHIFT => KeyCode::Shift,
winuser::VK_LWIN => KeyCode::Logo,
winuser::VK_RWIN => KeyCode::Logo,
winuser::VK_SPACE => KeyCode::Space,
winuser::VK_CAPITAL => KeyCode::Capslock,
winuser::VK_F1 => KeyCode::F1,
winuser::VK_F2 => KeyCode::F2,
winuser::VK_F3 => KeyCode::F3,
winuser::VK_F4 => KeyCode::F4,
winuser::VK_F5 => KeyCode::F5,
winuser::VK_F6 => KeyCode::F6,
winuser::VK_F7 => KeyCode::F7,
winuser::VK_F8 => KeyCode::F8,
winuser::VK_F9 => KeyCode::F9,
winuser::VK_F10 => KeyCode::F10,
winuser::VK_F11 => KeyCode::F11,
winuser::VK_F12 => KeyCode::F12,
winuser::VK_SNAPSHOT => KeyCode::PrintScreen,
winuser::VK_SCROLL => KeyCode::Scrolllock,
winuser::VK_PAUSE => KeyCode::Pause,
winuser::VK_INSERT => KeyCode::Insert,
winuser::VK_DELETE => KeyCode::Delete,
winuser::VK_HOME => KeyCode::Home,
winuser::VK_END => KeyCode::End,
winuser::VK_PRIOR => KeyCode::PageUp,
winuser::VK_NEXT => KeyCode::PageDown,
winuser::VK_NUMPAD0 => KeyCode::Numpad0,
winuser::VK_NUMPAD1 => KeyCode::Numpad1,
winuser::VK_NUMPAD2 => KeyCode::Numpad2,
winuser::VK_NUMPAD3 => KeyCode::Numpad3,
winuser::VK_NUMPAD4 => KeyCode::Numpad4,
winuser::VK_NUMPAD5 => KeyCode::Numpad5,
winuser::VK_NUMPAD6 => KeyCode::Numpad6,
winuser::VK_NUMPAD7 => KeyCode::Numpad7,
winuser::VK_NUMPAD8 => KeyCode::Numpad8,
winuser::VK_NUMPAD9 => KeyCode::Numpad9,
winuser::VK_SUBTRACT => KeyCode::NumpadSubtract,
winuser::VK_ADD => KeyCode::NumpadAdd,
winuser::VK_DECIMAL => KeyCode::NumpadDecimal,
winuser::VK_MULTIPLY => KeyCode::NumpadMultiply,
winuser::VK_DIVIDE => KeyCode::NumpadDivide,
winuser::VK_NUMLOCK => KeyCode::Numlock,
winuser::VK_UP => KeyCode::ArrowUp,
winuser::VK_DOWN => KeyCode::ArrowDown,
winuser::VK_LEFT => KeyCode::ArrowLeft,
winuser::VK_RIGHT => KeyCode::ArrowRight,
_ => KeyCode::Unknown,
}
}
}
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL;
type SetProcessDpiAwareness = unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT;
type SetProcessDpiAwarenessContext = unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL;
type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> UINT;
type GetDpiForMonitor =
unsafe extern "system" fn(hmonitor: HMONITOR, dpi_type: MONITOR_DPI_TYPE, dpi_x: *mut UINT, dpi_y: *mut UINT) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
( $ lib: expr, $ func: ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
};
}
pub(crate) struct DpiFunctions {
get_dpi_for_window: Option<GetDpiForWindow>,
get_dpi_for_monitor: Option<GetDpiForMonitor>,
enable_nonclient_dpi_scaling: Option<EnableNonClientDpiScaling>,
set_process_dpi_awareness_context: Option<SetProcessDpiAwarenessContext>,
set_process_dpi_awareness: Option<SetProcessDpiAwareness>,
set_process_dpi_aware: Option<SetProcessDPIAware>,
}
const BASE_DPI: u32 = 96;
impl DpiFunctions {
fn new() -> DpiFunctions {
DpiFunctions {
get_dpi_for_window: get_function!("user32.dll", GetDpiForWindow),
get_dpi_for_monitor: get_function!("shcore.dll", GetDpiForMonitor),
enable_nonclient_dpi_scaling: get_function!("user32.dll", EnableNonClientDpiScaling),
set_process_dpi_awareness_context: get_function!("user32.dll", SetProcessDpiAwarenessContext),
set_process_dpi_awareness: get_function!("shcore.dll", SetProcessDpiAwareness),
set_process_dpi_aware: get_function!("user32.dll", SetProcessDPIAware),
}
}
fn become_dpi_aware(&self) {
unsafe {
if let Some(set_process_dpi_awareness_context) = self.set_process_dpi_awareness_context {
if set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == FALSE {
set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
} else if let Some(set_process_dpi_awareness) = self.set_process_dpi_awareness {
set_process_dpi_awareness(PROCESS_PER_MONITOR_DPI_AWARE);
} else if let Some(set_process_dpi_aware) = self.set_process_dpi_aware {
set_process_dpi_aware();
}
}
}
pub(crate) fn enable_non_client_dpi_scaling(&self, hwnd: HWND) {
unsafe {
if let Some(enable_nonclient_dpi_scaling) = self.enable_nonclient_dpi_scaling {
enable_nonclient_dpi_scaling(hwnd);
}
}
}
pub(crate) fn hwnd_dpi_factor(&self, hwnd: HWND) -> f32 {
unsafe {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("`GetDC` returned null!");
}
let dpi = if let Some(get_dpi_for_window) = self.get_dpi_for_window {
match get_dpi_for_window(hwnd) {
0 => BASE_DPI, dpi => dpi as u32,
}
} else if let Some(get_dpi_for_monitor) = self.get_dpi_for_monitor {
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor.is_null() {
BASE_DPI
} else {
let mut dpi_x = 0;
let mut dpi_y = 0;
if get_dpi_for_monitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
}
} else {
if winuser::IsProcessDPIAware() != FALSE {
GetDeviceCaps(hdc, LOGPIXELSX) as u32
} else {
BASE_DPI
}
};
dpi as f32 / BASE_DPI as f32
}
}
}