#![allow(unsafe_code)]
use std::cell::RefCell;
use std::ffi::c_void;
use crate::geometry::{PhysicalSize, ScaleFactor};
use crate::platform::ControlFlow;
use crate::platform::error::PlatformError;
use crate::platform::event::{ButtonState, Event, KeyCode, PointerButton, WindowId};
use crate::platform::window::{Window, WindowAttributes};
use crate::render::{Pixmap, Surface};
type Handle = *mut c_void;
type Hwnd = Handle;
type Wparam = usize;
type Lparam = isize;
type Lresult = isize;
type WndProc = unsafe extern "system" fn(Hwnd, u32, Wparam, Lparam) -> Lresult;
const WS_OVERLAPPEDWINDOW: u32 = 0x00CF_0000;
const CW_USEDEFAULT: i32 = -2147483648; const SW_SHOW: i32 = 5;
const WM_DESTROY: u32 = 0x0002;
const WM_SIZE: u32 = 0x0005;
const WM_PAINT: u32 = 0x000F;
const WM_CLOSE: u32 = 0x0010;
const WM_KEYDOWN: u32 = 0x0100;
const WM_CHAR: u32 = 0x0102;
const WM_MOUSEMOVE: u32 = 0x0200;
const WM_LBUTTONDOWN: u32 = 0x0201;
const WM_LBUTTONUP: u32 = 0x0202;
const WM_RBUTTONDOWN: u32 = 0x0204;
const WM_RBUTTONUP: u32 = 0x0205;
const WM_MBUTTONDOWN: u32 = 0x0207;
const WM_MBUTTONUP: u32 = 0x0208;
const WM_GETOBJECT: u32 = 0x003D;
const UIA_ROOT_OBJECT_ID: i32 = -25;
const VK_BACK: usize = 0x08;
const VK_TAB: usize = 0x09;
const VK_RETURN: usize = 0x0D;
const VK_ESCAPE: usize = 0x1B;
const VK_LEFT: usize = 0x25;
const VK_UP: usize = 0x26;
const VK_RIGHT: usize = 0x27;
const VK_DOWN: usize = 0x28;
const BI_RGB: u32 = 0;
const DIB_RGB_COLORS: u32 = 0;
const SRCCOPY: u32 = 0x00CC_0020;
const IDC_ARROW: usize = 32512;
#[repr(C)]
struct WndClassW {
style: u32,
lpfn_wnd_proc: Option<WndProc>,
cb_cls_extra: i32,
cb_wnd_extra: i32,
h_instance: Handle,
h_icon: Handle,
h_cursor: Handle,
hbr_background: Handle,
lpsz_menu_name: *const u16,
lpsz_class_name: *const u16,
}
#[repr(C)]
struct Point {
x: i32,
y: i32,
}
#[repr(C)]
struct Msg {
hwnd: Hwnd,
message: u32,
w_param: Wparam,
l_param: Lparam,
time: u32,
pt: Point,
private: u32,
}
#[repr(C)]
struct Rect {
left: i32,
top: i32,
right: i32,
bottom: i32,
}
#[repr(C)]
struct PaintStruct {
hdc: Handle,
f_erase: i32,
rc_paint: Rect,
f_restore: i32,
f_inc_update: i32,
rgb_reserved: [u8; 32],
}
#[repr(C)]
struct BitmapInfoHeader {
bi_size: u32,
bi_width: i32,
bi_height: i32,
bi_planes: u16,
bi_bit_count: u16,
bi_compression: u32,
bi_size_image: u32,
bi_x_pels_per_meter: i32,
bi_y_pels_per_meter: i32,
bi_clr_used: u32,
bi_clr_important: u32,
}
#[repr(C)]
struct BitmapInfo {
header: BitmapInfoHeader,
colors: [u32; 1],
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleW(name: *const u16) -> Handle;
}
#[link(name = "uiautomationcore")]
unsafe extern "system" {
fn UiaReturnRawElementProvider(hwnd: Hwnd, wp: Wparam, lp: Lparam, el: *mut c_void) -> Lresult;
}
#[link(name = "user32")]
unsafe extern "system" {
fn RegisterClassW(class: *const WndClassW) -> u16;
fn LoadCursorW(instance: Handle, name: usize) -> Handle;
#[allow(clippy::too_many_arguments)]
fn CreateWindowExW(
ex_style: u32,
class_name: *const u16,
window_name: *const u16,
style: u32,
x: i32,
y: i32,
w: i32,
h: i32,
parent: Hwnd,
menu: Handle,
instance: Handle,
param: *mut c_void,
) -> Hwnd;
fn ShowWindow(hwnd: Hwnd, cmd: i32) -> i32;
fn UpdateWindow(hwnd: Hwnd) -> i32;
fn DefWindowProcW(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> Lresult;
fn GetMessageW(msg: *mut Msg, hwnd: Hwnd, min: u32, max: u32) -> i32;
fn TranslateMessage(msg: *const Msg) -> i32;
fn DispatchMessageW(msg: *const Msg) -> Lresult;
fn PostQuitMessage(code: i32);
fn DestroyWindow(hwnd: Hwnd) -> i32;
fn GetClientRect(hwnd: Hwnd, rect: *mut Rect) -> i32;
fn InvalidateRect(hwnd: Hwnd, rect: *const Rect, erase: i32) -> i32;
fn BeginPaint(hwnd: Hwnd, ps: *mut PaintStruct) -> Handle;
fn EndPaint(hwnd: Hwnd, ps: *const PaintStruct) -> i32;
}
#[link(name = "gdi32")]
unsafe extern "system" {
#[allow(clippy::too_many_arguments)]
fn StretchDIBits(
hdc: Handle,
x_dest: i32,
y_dest: i32,
w_dest: i32,
h_dest: i32,
x_src: i32,
y_src: i32,
w_src: i32,
h_src: i32,
bits: *const c_void,
bmi: *const BitmapInfo,
usage: u32,
rop: u32,
) -> i32;
}
#[derive(Default)]
struct WinCtx {
fb: Vec<u8>,
fb_w: i32,
fb_h: i32,
queue: Vec<Event>,
uia: Option<crate::platform::uia::UiaTree>,
}
fn push_event(ev: Event) {
CTX.with(|c| c.borrow_mut().queue.push(ev));
}
fn mouse_xy(lp: Lparam) -> crate::geometry::Point {
let x = (lp & 0xffff) as u16 as i16 as f64;
let y = ((lp >> 16) & 0xffff) as u16 as i16 as f64;
crate::geometry::Point::new(x, y)
}
fn map_vk(vk: usize) -> Option<KeyCode> {
Some(match vk {
VK_BACK => KeyCode::Backspace,
VK_TAB => KeyCode::Tab,
VK_RETURN => KeyCode::Enter,
VK_ESCAPE => KeyCode::Escape,
VK_LEFT => KeyCode::ArrowLeft,
VK_RIGHT => KeyCode::ArrowRight,
VK_UP => KeyCode::ArrowUp,
VK_DOWN => KeyCode::ArrowDown,
_ => return None,
})
}
thread_local! {
static CTX: RefCell<WinCtx> = RefCell::new(WinCtx::default());
}
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
unsafe extern "system" fn wnd_proc(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> Lresult {
match msg {
WM_PAINT => {
let mut ps: PaintStruct = unsafe { std::mem::zeroed() };
let hdc = unsafe { BeginPaint(hwnd, &mut ps) };
CTX.with(|c| {
let c = c.borrow();
if !c.fb.is_empty() {
let bmi = BitmapInfo {
header: BitmapInfoHeader {
bi_size: core::mem::size_of::<BitmapInfoHeader>() as u32,
bi_width: c.fb_w,
bi_height: -c.fb_h,
bi_planes: 1,
bi_bit_count: 32,
bi_compression: BI_RGB,
bi_size_image: 0,
bi_x_pels_per_meter: 0,
bi_y_pels_per_meter: 0,
bi_clr_used: 0,
bi_clr_important: 0,
},
colors: [0],
};
unsafe {
StretchDIBits(
hdc,
0,
0,
c.fb_w,
c.fb_h,
0,
0,
c.fb_w,
c.fb_h,
c.fb.as_ptr() as *const c_void,
&bmi,
DIB_RGB_COLORS,
SRCCOPY,
);
}
}
});
unsafe { EndPaint(hwnd, &ps) };
0
}
WM_SIZE => {
let w = (lp & 0xffff) as u32;
let h = ((lp >> 16) & 0xffff) as u32;
if w > 0 && h > 0 {
push_event(Event::Resized(PhysicalSize::new(w, h)));
}
0
}
WM_MOUSEMOVE => {
push_event(Event::PointerMoved {
position: mouse_xy(lp),
});
0
}
WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONUP
| WM_MBUTTONUP => {
let button = match msg {
WM_LBUTTONDOWN | WM_LBUTTONUP => PointerButton::Left,
WM_RBUTTONDOWN | WM_RBUTTONUP => PointerButton::Right,
_ => PointerButton::Middle,
};
let state = match msg {
WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => ButtonState::Pressed,
_ => ButtonState::Released,
};
push_event(Event::PointerButton {
button,
state,
position: mouse_xy(lp),
});
0
}
WM_KEYDOWN => {
if let Some(code) = map_vk(wp) {
push_event(Event::Key {
code,
state: ButtonState::Pressed,
modifiers: Default::default(),
});
}
0
}
WM_CHAR => {
if wp >= 0x20
&& let Some(ch) = char::from_u32(wp as u32)
{
push_event(Event::Text(ch.to_string()));
}
0
}
WM_GETOBJECT => {
if lp as i32 == UIA_ROOT_OBJECT_ID {
let root = CTX.with(|c| {
c.borrow()
.uia
.as_ref()
.map(|t| t.root_provider())
.unwrap_or(std::ptr::null_mut())
});
if !root.is_null() {
return unsafe { UiaReturnRawElementProvider(hwnd, wp, lp, root) };
}
}
unsafe { DefWindowProcW(hwnd, msg, wp, lp) }
}
WM_CLOSE => {
push_event(Event::CloseRequested);
0
}
WM_DESTROY => {
unsafe { PostQuitMessage(0) };
0
}
_ => unsafe { DefWindowProcW(hwnd, msg, wp, lp) },
}
}
#[derive(Debug)]
struct WinWindow {
hwnd: Hwnd,
size: PhysicalSize,
}
impl Window for WinWindow {
fn id(&self) -> WindowId {
WindowId(self.hwnd as u64)
}
fn inner_size(&self) -> PhysicalSize {
self.size
}
fn scale_factor(&self) -> ScaleFactor {
ScaleFactor::IDENTITY
}
fn request_redraw(&self) {
unsafe { InvalidateRect(self.hwnd, std::ptr::null(), 0) };
}
fn set_title(&self, _title: &str) {}
fn create_surface(&self) -> Box<dyn Surface> {
Box::new(WinSurface {
hwnd: self.hwnd,
size: self.size,
})
}
fn set_accessibility_tree(&self, root: &crate::platform::access::A11yNode) {
let tree = crate::platform::uia::UiaTree::build(root);
CTX.with(|c| c.borrow_mut().uia = Some(tree));
}
}
#[derive(Debug)]
struct WinSurface {
hwnd: Hwnd,
size: PhysicalSize,
}
impl Surface for WinSurface {
fn resize(&mut self, size: PhysicalSize) {
self.size = size;
}
fn size(&self) -> PhysicalSize {
self.size
}
fn present(&mut self, pixmap: &Pixmap, _damage: &[crate::geometry::Rect]) {
let size = pixmap.size();
let src = pixmap.as_bytes();
let mut fb = vec![0u8; src.len()];
for (d, s) in fb
.as_chunks_mut::<4>()
.0
.iter_mut()
.zip(src.as_chunks::<4>().0)
{
d[0] = s[2];
d[1] = s[1];
d[2] = s[0];
d[3] = s[3];
}
CTX.with(|c| {
let mut c = c.borrow_mut();
c.fb = fb;
c.fb_w = size.width as i32;
c.fb_h = size.height as i32;
});
unsafe { InvalidateRect(self.hwnd, std::ptr::null(), 0) };
}
}
pub fn run<H>(attrs: WindowAttributes, mut handler: H) -> Result<(), PlatformError>
where
H: FnMut(Event, &dyn Window) -> ControlFlow,
{
let class_name = wide("StippleWindowClass");
let title = wide(&attrs.title);
let size = ScaleFactor::IDENTITY.to_physical(attrs.logical_size);
let hinstance = unsafe { GetModuleHandleW(std::ptr::null()) };
let cursor = unsafe { LoadCursorW(std::ptr::null_mut(), IDC_ARROW) };
let class = WndClassW {
style: 0,
lpfn_wnd_proc: Some(wnd_proc),
cb_cls_extra: 0,
cb_wnd_extra: 0,
h_instance: hinstance,
h_icon: std::ptr::null_mut(),
h_cursor: cursor,
hbr_background: std::ptr::null_mut(),
lpsz_menu_name: std::ptr::null(),
lpsz_class_name: class_name.as_ptr(),
};
let atom = unsafe { RegisterClassW(&class) };
if atom == 0 {
return Err(PlatformError::Os("RegisterClassW failed".into()));
}
let hwnd = unsafe {
CreateWindowExW(
0,
class_name.as_ptr(),
title.as_ptr(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
size.width as i32,
size.height as i32,
std::ptr::null_mut(),
std::ptr::null_mut(),
hinstance,
std::ptr::null_mut(),
)
};
if hwnd.is_null() {
return Err(PlatformError::Os("CreateWindowExW failed".into()));
}
let mut rect = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
unsafe { GetClientRect(hwnd, &mut rect) };
let client = PhysicalSize::new(
(rect.right - rect.left).max(1) as u32,
(rect.bottom - rect.top).max(1) as u32,
);
let win = WinWindow { hwnd, size: client };
handler(Event::RedrawRequested, &win);
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
let mut msg: Msg = unsafe { std::mem::zeroed() };
loop {
let r = unsafe { GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0) };
if r <= 0 {
break;
}
unsafe {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
let events: Vec<Event> = CTX.with(|c| std::mem::take(&mut c.borrow_mut().queue));
for event in events {
if handler(event, &win) == ControlFlow::Exit {
unsafe { DestroyWindow(hwnd) };
}
}
}
Ok(())
}