#![allow(non_snake_case)]
use std::ffi::OsStr;
use std::mem;
use std::ptr::{null, null_mut};
use std::rc::Rc;
use winapi::ctypes::c_int;
use winapi::shared::minwindef::{ATOM, DWORD, HINSTANCE, LPARAM, LPVOID, LRESULT, UINT, WPARAM};
use winapi::shared::windef::{HBRUSH, HCURSOR, HICON, HMENU, HWND};
use winapi::um::winnt::LPCWSTR;
use winapi::um::winuser::{
CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, RegisterClassExW, SetWindowLongPtrW,
CREATESTRUCTW, CW_USEDEFAULT, GWLP_USERDATA, WM_CREATE, WM_NCDESTROY, WNDCLASSEXW,
};
use wio::wide::ToWide;
use crate::error::Error;
pub trait WindowProc {
fn window_proc(&self, hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM)
-> Option<LRESULT>;
}
pub enum WindowClass {
Atom(ATOM),
Name(Vec<u16>),
}
pub struct WindowClassBuilder {
style: UINT,
cbWndExtra: c_int,
hInstance: HINSTANCE,
hIcon: HICON,
hCursor: HCURSOR,
hbrBackground: HBRUSH,
menu_name: Vec<u16>,
class_name: Vec<u16>,
hIconSm: HICON,
}
pub struct WindowBuilder<'a> {
window_proc: Rc<Box<dyn WindowProc>>,
dwExStyle: DWORD,
window_class: &'a WindowClass,
window_name: Vec<u16>,
dwStyle: DWORD,
x: c_int,
y: c_int,
nWidth: c_int,
nHeight: c_int,
hWndParent: HWND,
hMenu: HMENU,
hInstance: HINSTANCE,
}
impl<'a> WindowBuilder<'a> {
pub fn new(
window_proc: impl WindowProc + 'static,
window_class: &WindowClass,
) -> WindowBuilder {
WindowBuilder {
window_proc: Rc::new(Box::new(window_proc)),
dwExStyle: 0,
window_class,
window_name: Vec::new(),
dwStyle: 0,
x: CW_USEDEFAULT,
y: CW_USEDEFAULT,
nWidth: CW_USEDEFAULT,
nHeight: CW_USEDEFAULT,
hWndParent: null_mut(),
hMenu: null_mut(),
hInstance: null_mut(),
}
}
pub fn build(self) -> HWND {
unsafe {
let wnd_proc_ptr = Rc::into_raw(self.window_proc) as LPVOID;
let hwnd = CreateWindowExW(
self.dwExStyle,
self.window_class.as_lpcwstr(),
pointer_or_null(&self.window_name),
self.dwStyle,
self.x,
self.y,
self.nWidth,
self.nHeight,
self.hWndParent,
self.hMenu,
self.hInstance,
wnd_proc_ptr,
);
if hwnd.is_null() {
std::mem::drop(Rc::from_raw(wnd_proc_ptr));
}
hwnd
}
}
pub fn name(mut self, name: impl AsRef<OsStr>) -> Self {
self.window_name = name.to_wide_null();
self
}
pub fn style(mut self, style: DWORD) -> Self {
self.dwStyle = style;
self
}
pub fn ex_style(mut self, style: DWORD) -> Self {
self.dwExStyle = style;
self
}
pub fn position(mut self, x: c_int, y: c_int) -> Self {
self.x = x;
self.y = y;
self
}
pub fn size(mut self, width: c_int, height: c_int) -> Self {
self.nWidth = width;
self.nHeight = height;
self
}
pub unsafe fn parent_hwnd(mut self, parent: HWND) -> Self {
self.hWndParent = parent;
self
}
pub unsafe fn menu(mut self, menu: HMENU) -> Self {
self.hMenu = menu;
self
}
pub unsafe fn instance(mut self, instance: HINSTANCE) -> Self {
self.hInstance = instance;
self
}
}
#[cfg(target_arch = "x86_64")]
type WindowLongPtr = winapi::shared::basetsd::LONG_PTR;
#[cfg(target_arch = "x86")]
type WindowLongPtr = winapi::shared::ntdef::LONG;
unsafe extern "system" fn raw_window_proc(
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_CREATE {
let create_struct = &*(lparam as *const CREATESTRUCTW);
let window_state_ptr = create_struct.lpCreateParams;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, window_state_ptr as WindowLongPtr);
}
let window_proc_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *const Box<dyn WindowProc>;
let result = {
if window_proc_ptr.is_null() {
None
} else {
let reference = Rc::from_raw(window_proc_ptr);
mem::forget(reference.clone());
(*window_proc_ptr).window_proc(hwnd, msg, wparam, lparam)
}
};
if msg == WM_NCDESTROY && !window_proc_ptr.is_null() {
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
mem::drop(Rc::from_raw(window_proc_ptr));
}
result.unwrap_or_else(|| DefWindowProcW(hwnd, msg, wparam, lparam))
}
impl WindowClass {
pub fn builder(class_name: impl AsRef<OsStr>) -> WindowClassBuilder {
WindowClassBuilder {
class_name: class_name.to_wide_null(),
style: 0,
cbWndExtra: 0,
hInstance: null_mut(),
hIcon: null_mut(),
hCursor: null_mut(),
hbrBackground: null_mut(),
menu_name: Vec::new(),
hIconSm: null_mut(),
}
}
pub fn from_name(class_name: impl AsRef<OsStr>) -> WindowClass {
WindowClass::Name(class_name.to_wide_null())
}
fn as_lpcwstr(&self) -> LPCWSTR {
match self {
WindowClass::Atom(atom) => *atom as LPCWSTR,
WindowClass::Name(name) => name.as_ptr(),
}
}
}
impl WindowClassBuilder {
pub fn build(self) -> Result<WindowClass, Error> {
unsafe {
let wnd = WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
style: self.style,
lpfnWndProc: Some(raw_window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: self.hInstance,
hIcon: self.hIcon,
hCursor: self.hCursor,
hbrBackground: self.hbrBackground,
lpszMenuName: pointer_or_null(&self.menu_name),
lpszClassName: self.class_name.as_ptr(),
hIconSm: self.hIconSm,
};
let class_atom = RegisterClassExW(&wnd);
if class_atom == 0 {
Err(Error::RegisterClassFailed)
} else {
Ok(WindowClass::Atom(class_atom))
}
}
}
pub fn class_style(mut self, style: DWORD) -> Self {
self.style = style;
self
}
pub unsafe fn wnd_extra_bytes(mut self, extra_bytes: c_int) -> Self {
self.cbWndExtra = extra_bytes;
self
}
pub unsafe fn instance(mut self, instance: HINSTANCE) -> Self {
self.hInstance = instance;
self
}
pub unsafe fn icon(mut self, icon: HICON) -> Self {
self.hIcon = icon;
self
}
pub unsafe fn small_icon(mut self, icon: HICON) -> Self {
self.hIconSm = icon;
self
}
pub unsafe fn cursor(mut self, cursor: HCURSOR) -> Self {
self.hCursor = cursor;
self
}
pub unsafe fn background(mut self, brush: HBRUSH) -> Self {
self.hbrBackground = brush;
self
}
pub fn menu_name(mut self, menu_name: impl AsRef<OsStr>) -> Self {
self.menu_name = menu_name.to_wide_null();
self
}
pub const DLGWINDOWEXTRA: c_int = 30;
}
fn pointer_or_null(slice: &[u16]) -> *const u16 {
if slice.is_empty() {
null()
} else {
slice.as_ptr()
}
}