use std::cell::RefCell;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Once;
use denise::{ElementState, InputEvent, Modifiers, Point, PointerButton, Rect, Size, Surface};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM};
use windows::Win32::Graphics::Gdi::{BeginPaint, EndPaint, InvalidateRect, PAINTSTRUCT};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::Input::KeyboardAndMouse::{
GetKeyState, ReleaseCapture, SetCapture, SetFocus, TME_LEAVE, TRACKMOUSEEVENT, TrackMouseEvent,
VK_CONTROL, VK_LWIN, VK_MENU, VK_RWIN, VK_SHIFT,
};
use windows::Win32::UI::Controls::WM_MOUSELEAVE;
use windows::Win32::UI::WindowsAndMessaging::{
CREATESTRUCTW, CS_DBLCLKS, CS_HREDRAW, CS_VREDRAW, CreateWindowExW, DLGC_WANTALLKEYS,
DLGC_WANTARROWS, DLGC_WANTCHARS, DLGC_WANTTAB, DefWindowProcW, GWLP_USERDATA, RegisterClassExW,
WHEEL_DELTA, WINDOW_EX_STYLE, WM_CHAR, WM_ERASEBKGND, WM_GETDLGCODE, WM_KEYDOWN, WM_KEYUP,
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
WM_MOUSEWHEEL, WM_NCCREATE, WM_NCDESTROY, WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE,
WM_SYSKEYDOWN, WM_SYSKEYUP, WNDCLASSEXW, WS_CHILD, WS_CLIPCHILDREN, WS_VISIBLE,
};
#[cfg(target_pointer_width = "64")]
use windows::Win32::UI::WindowsAndMessaging::{GetWindowLongPtrW, SetWindowLongPtrW};
#[cfg(not(target_pointer_width = "64"))]
use windows::Win32::UI::WindowsAndMessaging::{GetWindowLongW, SetWindowLongW};
use windows::core::{PCWSTR, w};
use crate::Error;
use crate::keymap::key_code;
use crate::surface::{DibSurface, screen_to_client};
const LINE_HEIGHT_PX: f32 = 16.0;
const CLASS_NAME: PCWSTR = w!("Denise.Control");
static REGISTER: Once = Once::new();
pub trait ControlDelegate {
fn update(&mut self, surface: &mut DibSurface, events: &[InputEvent], damage: &mut Vec<Rect>);
fn next_wake_ms(&self) -> Option<u64> {
None
}
}
struct ControlState {
surface: DibSurface,
delegate: Box<dyn ControlDelegate>,
events: Vec<InputEvent>,
damage: Vec<Rect>,
tracking: bool,
high_surrogate: Option<u16>,
}
impl ControlState {
fn push(&mut self, event: InputEvent) {
self.events.push(event);
}
}
#[derive(Clone, Copy, Debug)]
pub struct DeniseControl {
hwnd: HWND,
}
impl DeniseControl {
pub fn new(
parent: HWND,
bounds: Rect,
scale_factor: f32,
delegate: Box<dyn ControlDelegate>,
) -> Result<Self, Error> {
register_class()?;
let size = Size::new(bounds.width.max(0) as u32, bounds.height.max(0) as u32);
let surface = DibSurface::new(size, scale_factor)?;
let state = Box::new(RefCell::new(ControlState {
surface,
delegate,
events: Vec::new(),
damage: Vec::new(),
tracking: false,
high_surrogate: None,
}));
let hwnd = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
CLASS_NAME,
PCWSTR::null(),
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,
bounds.x,
bounds.y,
bounds.width,
bounds.height,
Some(parent),
None,
None,
Some(Box::into_raw(state).cast()),
)
}
.map_err(|_| Error::CreateWindow)?;
Ok(Self { hwnd })
}
#[inline]
pub const fn hwnd(&self) -> HWND {
self.hwnd
}
pub fn update(&self) {
let Some(state) = (unsafe { state_of(self.hwnd) }) else {
return;
};
let rects = {
let mut borrow = state.borrow_mut();
let state = &mut *borrow;
state.damage.clear();
let events = core::mem::take(&mut state.events);
state
.delegate
.update(&mut state.surface, &events, &mut state.damage);
state.events = events;
state.events.clear();
state.damage.clone()
};
for rect in rects {
let native = RECT {
left: rect.x,
top: rect.y,
right: rect.x + rect.width,
bottom: rect.y + rect.height,
};
unsafe {
let _ = InvalidateRect(Some(self.hwnd), Some(&native), false);
}
}
}
pub fn next_wake_ms(&self) -> Option<u64> {
let state = unsafe { state_of(self.hwnd) }?;
state.borrow().delegate.next_wake_ms()
}
pub fn set_scale_factor(&self, scale_factor: f32) {
let Some(state) = (unsafe { state_of(self.hwnd) }) else {
return;
};
let size = state.borrow().surface.size();
let changed = state
.borrow_mut()
.surface
.resize(size, scale_factor)
.unwrap_or(false);
if changed {
unsafe {
let _ = InvalidateRect(Some(self.hwnd), None, false);
}
}
}
}
fn register_class() -> Result<(), Error> {
let mut result = Ok(());
REGISTER.call_once(|| {
let instance = match unsafe { GetModuleHandleW(None) } {
Ok(handle) => handle,
Err(_) => {
result = Err(Error::RegisterClass);
return;
}
};
let class = WNDCLASSEXW {
cbSize: size_of::<WNDCLASSEXW>() as u32,
style: CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
lpfnWndProc: Some(wnd_proc),
hInstance: instance.into(),
hbrBackground: Default::default(),
lpszClassName: CLASS_NAME,
..Default::default()
};
if unsafe { RegisterClassExW(&class) } == 0 {
result = Err(Error::RegisterClass);
}
});
result
}
unsafe fn state_of<'a>(hwnd: HWND) -> Option<&'a RefCell<ControlState>> {
if hwnd.is_invalid() {
return None;
}
let pointer = unsafe { get_user_data(hwnd) } as *const RefCell<ControlState>;
unsafe { pointer.as_ref() }
}
#[cfg(target_pointer_width = "64")]
unsafe fn get_user_data(hwnd: HWND) -> isize {
unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) }
}
#[cfg(target_pointer_width = "64")]
unsafe fn set_user_data(hwnd: HWND, value: isize) {
unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, value) };
}
#[cfg(not(target_pointer_width = "64"))]
unsafe fn get_user_data(hwnd: HWND) -> isize {
unsafe { GetWindowLongW(hwnd, GWLP_USERDATA) as isize }
}
#[cfg(not(target_pointer_width = "64"))]
unsafe fn set_user_data(hwnd: HWND, value: isize) {
unsafe { SetWindowLongW(hwnd, GWLP_USERDATA, value as i32) };
}
extern "system" fn wnd_proc(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let handled = catch_unwind(AssertUnwindSafe(|| {
unsafe { handle(hwnd, message, wparam, lparam) }
}));
match handled {
Ok(Some(result)) => result,
Ok(None) | Err(_) => {
unsafe { DefWindowProcW(hwnd, message, wparam, lparam) }
}
}
}
unsafe fn handle(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> Option<LRESULT> {
match message {
WM_NCCREATE => {
let create = unsafe { &*(lparam.0 as *const CREATESTRUCTW) };
unsafe { set_user_data(hwnd, create.lpCreateParams as isize) };
None
}
WM_NCDESTROY => {
let pointer = unsafe { get_user_data(hwnd) } as *mut RefCell<ControlState>;
unsafe { set_user_data(hwnd, 0) };
if !pointer.is_null() {
drop(unsafe { Box::from_raw(pointer) });
}
None
}
WM_PAINT => {
let mut paint = PAINTSTRUCT::default();
let dc = unsafe { BeginPaint(hwnd, &mut paint) };
if let Some(state) = unsafe { state_of(hwnd) } {
let clip = Rect::new(
paint.rcPaint.left,
paint.rcPaint.top,
paint.rcPaint.right - paint.rcPaint.left,
paint.rcPaint.bottom - paint.rcPaint.top,
);
unsafe { state.borrow().surface.blit(dc, &[clip]) };
}
unsafe {
let _ = EndPaint(hwnd, &paint);
};
Some(LRESULT(0))
}
WM_ERASEBKGND => Some(LRESULT(1)),
WM_SIZE => {
let width = (lparam.0 & 0xFFFF) as u32;
let height = ((lparam.0 >> 16) & 0xFFFF) as u32;
if let Some(state) = unsafe { state_of(hwnd) } {
let scale = state.borrow().surface.scale_factor();
let changed = state
.borrow_mut()
.surface
.resize(Size::new(width, height), scale)
.unwrap_or(false);
if changed {
state.borrow_mut().push(InputEvent::SurfaceResized {
size: Size::new(width, height),
scale_factor: scale,
});
unsafe {
let _ = InvalidateRect(Some(hwnd), None, false);
};
}
}
unsafe { control(hwnd).update() };
Some(LRESULT(0))
}
WM_GETDLGCODE => Some(LRESULT(
(DLGC_WANTALLKEYS | DLGC_WANTCHARS | DLGC_WANTARROWS | DLGC_WANTTAB) as isize,
)),
WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => {
unsafe {
let _ = SetFocus(Some(hwnd));
};
unsafe { SetCapture(hwnd) };
let button = match message {
WM_RBUTTONDOWN => PointerButton::Right,
WM_MBUTTONDOWN => PointerButton::Middle,
_ => PointerButton::Left,
};
unsafe { pointer_button(hwnd, lparam, button, ElementState::Down) };
Some(LRESULT(0))
}
WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP => {
unsafe {
let _ = ReleaseCapture();
};
let button = match message {
WM_RBUTTONUP => PointerButton::Right,
WM_MBUTTONUP => PointerButton::Middle,
_ => PointerButton::Left,
};
unsafe { pointer_button(hwnd, lparam, button, ElementState::Up) };
Some(LRESULT(0))
}
WM_MOUSEMOVE => {
if let Some(state) = unsafe { state_of(hwnd) } {
if !state.borrow().tracking {
let mut track = TRACKMOUSEEVENT {
cbSize: size_of::<TRACKMOUSEEVENT>() as u32,
dwFlags: TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: 0,
};
if unsafe { TrackMouseEvent(&mut track) }.is_ok() {
state.borrow_mut().tracking = true;
}
}
let position = client_point(lparam);
state
.borrow_mut()
.push(InputEvent::PointerMoved { position });
}
unsafe { control(hwnd).update() };
Some(LRESULT(0))
}
WM_MOUSELEAVE => {
if let Some(state) = unsafe { state_of(hwnd) } {
state.borrow_mut().tracking = false;
state.borrow_mut().push(InputEvent::PointerLeft);
}
unsafe { control(hwnd).update() };
Some(LRESULT(0))
}
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
let (x, y) = screen_to_client(hwnd, x, y);
let notches = ((wparam.0 >> 16) & 0xFFFF) as i16 as f32 / WHEEL_DELTA as f32;
let (delta_x, delta_y) = if message == WM_MOUSEHWHEEL {
(notches * LINE_HEIGHT_PX, 0.0)
} else {
(0.0, -notches * LINE_HEIGHT_PX)
};
if let Some(state) = unsafe { state_of(hwnd) } {
state.borrow_mut().push(InputEvent::PointerScroll {
delta_x,
delta_y,
position: Point::new(x, y),
});
}
unsafe { control(hwnd).update() };
Some(LRESULT(0))
}
WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => {
let down = message == WM_KEYDOWN || message == WM_SYSKEYDOWN;
let extended = lparam.0 & (1 << 24) != 0;
let repeat = down && lparam.0 & (1 << 30) != 0;
let code = key_code(wparam.0 as u16, extended);
if let Some(state) = unsafe { state_of(hwnd) } {
state.borrow_mut().push(InputEvent::Key {
code,
state: if down {
ElementState::Down
} else {
ElementState::Up
},
repeat,
modifiers: current_modifiers(),
});
}
unsafe { control(hwnd).update() };
if message == WM_SYSKEYDOWN || message == WM_SYSKEYUP {
Some(LRESULT(0))
} else {
None
}
}
WM_CHAR => {
let unit = wparam.0 as u16;
if let Some(state) = unsafe { state_of(hwnd) } {
let mut borrow = state.borrow_mut();
let scalar = match (borrow.high_surrogate.take(), unit) {
(_, 0xD800..=0xDBFF) => {
borrow.high_surrogate = Some(unit);
None
}
(Some(high), 0xDC00..=0xDFFF) => {
let combined =
0x1_0000 + ((high as u32 - 0xD800) << 10) + (unit as u32 - 0xDC00);
char::from_u32(combined)
}
(None, 0xDC00..=0xDFFF) => None,
_ => char::from_u32(unit as u32),
};
if let Some(ch) = scalar.filter(|c| !c.is_control()) {
borrow.push(InputEvent::Text { ch });
}
}
unsafe { control(hwnd).update() };
Some(LRESULT(0))
}
_ => None,
}
}
unsafe fn control(hwnd: HWND) -> DeniseControl {
DeniseControl { hwnd }
}
fn client_point(lparam: LPARAM) -> Point {
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
Point::new(x, y)
}
unsafe fn pointer_button(hwnd: HWND, lparam: LPARAM, button: PointerButton, element: ElementState) {
if let Some(state) = unsafe { state_of(hwnd) } {
state.borrow_mut().push(InputEvent::PointerButton {
button,
state: element,
position: client_point(lparam),
modifiers: current_modifiers(),
});
}
unsafe { control(hwnd).update() };
}
fn current_modifiers() -> Modifiers {
let mut out = Modifiers::NONE;
for (key, modifier) in [
(VK_SHIFT, Modifiers::SHIFT),
(VK_CONTROL, Modifiers::CTRL),
(VK_MENU, Modifiers::ALT),
] {
if unsafe { GetKeyState(key.0 as i32) } < 0 {
out |= modifier;
}
}
for key in [VK_LWIN, VK_RWIN] {
if unsafe { GetKeyState(key.0 as i32) } < 0 {
out |= Modifiers::SUPER;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_drag_off_the_left_edge_reports_a_negative_x() {
let packed = LPARAM(((10i32 as u32) << 16 | (-3i32 as u32 & 0xFFFF)) as isize);
assert_eq!(client_point(packed), Point::new(-3, 10));
}
#[test]
fn a_position_inside_the_window_survives_unpacking() {
let packed = LPARAM(((400i32 as u32) << 16 | 250) as isize);
assert_eq!(client_point(packed), Point::new(250, 400));
}
}