#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
extern crate alloc;
use core::{cell::RefCell, ffi::{c_ulong, c_ushort, CStr}, marker::PhantomData};
use alloc::{rc::Rc, boxed::Box, collections::VecDeque, ffi::CString};
use windows::{core::PCSTR, Win32::{Foundation::*, UI::WindowsAndMessaging::*}};
use windows::Win32::System::LibraryLoader::{GetModuleHandleA, LoadLibraryA};
use windows::Win32::Graphics::Gdi::{HBRUSH, HDC, GetDC, ValidateRect};
use windows::System::VirtualKey;
use crate::{set_window_long, get_window_long};
#[derive(Default)]
pub struct WindowBuilder<'a>
{
title: Option<&'a CStr>,
position: Position,
size: Size,
paint_func: Option<PaintFn>
}
impl<'a> WindowBuilder<'a>
{
pub fn new() -> Self
{
Self
{
position: Position::new(CW_USEDEFAULT, CW_USEDEFAULT),
..Default::default()
}
}
pub fn with_title(mut self, title: &'a CStr) -> Self
{
self.title = Some(title);
self
}
pub fn with_size(mut self, size: Size) -> Self
{
self.size = size;
self
}
pub fn with_paint_func(mut self, paint_func: PaintFn) -> Self
{
self.paint_func = Some(paint_func);
self
}
pub fn with_position(mut self, position: Position) -> Self
{
self.position = position;
self
}
pub fn build(self) -> windows::core::Result<Window<NotCurrent>>
{
Window::create(self)
}
}
pub type PaintFn = Box<dyn Fn(&Box<ContextTarget>)>;
pub struct Window<S: CurrentState>
{
handle: HWND,
hdc: HDC,
data: *mut ContextTarget,
gl_context: GLContext,
events: Rc<RefCell<VecDeque<Event>>>,
_marker: PhantomData<S>
}
impl Window<NotCurrent>
{
pub fn make_current(mut self) -> Result<Window<PossibleCurrent>, windows::core::Error>
{
self.gl_context.make_current(self.hdc)?;
unsafe { core::mem::transmute(self) }
}
fn PixelFormatDescriptor() -> PIXELFORMATDESCRIPTOR
{
PIXELFORMATDESCRIPTOR
{
dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
iPixelType: PFD_TYPE_RGBA,
cColorBits: 32,
iLayerType: PFD_MAIN_PLANE,
..Default::default()
}
}
}
impl<S: CurrentState> Window<S>
{
fn create(builder: WindowBuilder) -> windows::core::Result<Self>
{
unsafe { SetProcessDPIAware(); }
let size: Size = builder.size;
let gl_context = GLContext::new();
let (handle, hdc, data) = unsafe { Self::create_window(size, builder)? };
Ok(Self
{
handle,
hdc,
data,
events: unsafe { (*data).events.clone() },
gl_context,
_marker: Default::default()
})
}
unsafe fn create_window(size: Size, builder: WindowBuilder) -> windows::core::Result<(HWND, HDC, *mut ContextTarget)>
{
let instance = GetModuleHandleA(None)?;
let window_class = PCSTR::from_raw(c"Wingl Window".as_ptr() as *const u8);
let wc = WNDCLASSA
{
hCursor: LoadCursorW(None, IDC_ARROW)?,
hInstance: instance.into(),
lpszClassName: window_class,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(Window::<S>::event_callback),
hbrBackground: HBRUSH(2), ..Default::default()
};
let atom = RegisterClassA(&wc);
debug_assert!(atom != 0);
let mut rect = RECT { left: 0, top: 0, right: size.width as i32, bottom: size.height as i32 };
AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, None)?;
let title = PCSTR::from_raw(builder.title.map_or(core::ptr::null(), |t|t.as_ptr() as *const u8));
let handle: HWND = CreateWindowExA(
WINDOW_EX_STYLE::default(),
window_class,
title,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
builder.position.x,
builder.position.y,
rect.right - rect.left,
rect.bottom - rect.top,
None,
None,
instance,
None,
);
let mut data = Box::new(ContextTarget::default());
let hdc = GetDC(handle);
data.hdc = hdc;
data.window_size = size;
data.paint_func = builder.paint_func;
let data = Box::into_raw(data);
set_window_long(handle, GWL_USERDATA, data as isize);
let pfd = Window::PixelFormatDescriptor();
let pixel_format = ChoosePixelFormat(hdc, &pfd);
assert!(SetPixelFormat(hdc, pixel_format, &pfd) == true);
Ok((handle, hdc, data))
}
pub fn request_gl_version(&mut self, version: (i32, i32)) -> bool
{
self.gl_context.request_gl_version(self.hdc, version)
}
pub fn poll(&mut self) -> bool
{
unsafe
{
let mut msg = MSG::default();
while PeekMessageA(&mut msg, None, 0, 0, PM_REMOVE).into()
{
DispatchMessageA(&msg);
}
}
true
}
pub fn next(&mut self) -> Option<Event>
{
self.events.borrow_mut().pop_front()
}
pub fn swap_buffers(&self)
{
unsafe { SwapBuffers(self.hdc) };
}
pub fn size(&self) -> Size
{
unsafe { (*self.data).window_size }
}
pub fn resize(&mut self, size: Size)
{
let mut w = size.width as i32;
let mut h = size.height as i32;
let mut rect = RECT { top: 0, left: 0, right: w, bottom: h };
unsafe
{
if AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, None).is_ok()
{
w = rect.right - rect.left;
h = rect.bottom - rect.top;
SetWindowPos(self.handle, None, 0, 0, w, h, SWP_NOMOVE).expect("Resize failed");
}
}
}
pub fn handle(&self) -> HWND
{
self.handle
}
pub fn set_paint_callback(&mut self, paint_func: PaintFn)
{
unsafe
{
(*self.data).paint_func = Some(paint_func);
}
}
unsafe extern "system" fn event_callback(window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT
{
let data_ptr = get_window_long(window, GWL_USERDATA) as *mut ContextTarget;
if data_ptr.is_null()
{
return DefWindowProcA(window, msg, wparam, lparam);
}
let mut context_target = Box::from_raw(data_ptr);
let result = match msg
{
WM_PAINT => {
ValidateRect(window, None);
if let Some(f) = &context_target.paint_func
{
f(&context_target);
SwapBuffers(context_target.hdc);
}
LRESULT(0)
},
WM_CLOSE => {
let _ = DestroyWindow(window);
LRESULT(0)
}
WM_NCDESTROY => {
set_window_long(window, GWL_USERDATA, 0);
LRESULT(0)
}
WM_DESTROY => {
PostQuitMessage(0);
context_target.send(Event::Exit);
LRESULT(0)
},
WM_KEYDOWN => {
context_target.send(Event::KeyPressed(VirtualKey(wparam.0 as i32)));
LRESULT(0)
}
WM_KEYUP => {
context_target.send(Event::KeyReleased(VirtualKey(wparam.0 as i32)));
LRESULT(0)
}
WM_WINDOWPOSCHANGED => {
let new_pos = lparam.0 as *const WINDOWPOS;
let mut w = (*new_pos).cx;
let mut h = (*new_pos).cy;
let mut rect = RECT::default();
if AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, None).is_ok()
{
w -= rect.right - rect.left;
h -= rect.bottom - rect.top;
}
let size = Size::new(w as i16, h as i16);
context_target.window_size = size;
context_target.send(Event::Resized(size));
LRESULT(0)
}
WM_NCCREATE => LRESULT(1),
WM_CREATE => LRESULT(0),
_ => DefWindowProcA(window, msg, wparam, lparam),
};
let _ = Box::into_raw(context_target);
result
}
}
impl Window<PossibleCurrent>
{
pub fn gl_context(&self) -> &GLContext
{
&self.gl_context
}
}
impl<S: CurrentState> Drop for Window<S>
{
fn drop(&mut self)
{
unsafe
{
if let Some(handle) = self.gl_context.current_handle
{
wglDeleteContext(handle);
}
drop(Box::from_raw(self.data));
}
}
}
#[derive(Default, Debug, PartialEq, Clone, Copy)]
pub struct Size
{
pub width: i16,
pub height: i16,
}
impl Size
{
pub fn new(width: i16, height: i16) -> Size
{
Self { width, height }
}
}
#[derive(Default, Debug, PartialEq, Clone, Copy)]
pub struct Position
{
pub x: i32,
pub y: i32,
}
impl Position
{
pub fn new(x: i32, y: i32) -> Position
{
Self { x, y }
}
}
#[repr(C)]
#[derive(Default)]
pub struct ContextTarget
{
hdc: HDC,
window_size: Size,
events: Rc<RefCell<VecDeque<Event>>>,
paint_func: Option<PaintFn>,
}
impl ContextTarget
{
pub fn send(&self, ev: Event)
{
self.events.borrow_mut().push_back(ev);
}
pub fn GetDC(&self) -> HDC
{
self.hdc
}
pub fn size(&self) -> Size
{
self.window_size
}
}
#[derive(Debug, PartialEq)]
pub enum Event
{
KeyPressed(VirtualKey),
KeyReleased(VirtualKey),
Resized(Size),
Exit
}
pub trait CurrentState{}
pub struct NotCurrent;
pub struct PossibleCurrent;
impl CurrentState for NotCurrent{}
impl CurrentState for PossibleCurrent{}
pub struct GLContext
{
opengl32: HMODULE,
current_handle: Option<HANDLE>
}
impl GLContext
{
fn new() -> Self
{
unsafe
{
let opengl32 = LoadLibraryA(PCSTR(c"opengl32.dll".as_ptr() as *const u8)).unwrap();
Self
{
opengl32,
current_handle: None
}
}
}
pub fn is_current(&self) -> bool
{
self.current_handle.is_some()
}
fn make_current(&mut self, hdc: HDC) -> Result<(), windows::core::Error>
{
unsafe
{
let gl_context = wglCreateContext(hdc);
if !wglMakeCurrent(hdc, gl_context).as_bool()
{
return GetLastError();
}
self.current_handle = Some(gl_context);
}
Ok(())
}
pub fn request_gl_version(&mut self, hdc: HDC, version: (i32, i32)) -> bool
{
unsafe
{
if let Some(handle) = self.current_handle
{
let wglCreateContextAttribsARB: wglCreateContextAttribsARB_t = core::mem::transmute(self.get_proc_address(&"wglCreateContextAttribsARB"));
if let Some(create) = wglCreateContextAttribsARB
{
let attributs = [
WGL_CONTEXT_MAJOR_VERSION_ARB, version.0,
WGL_CONTEXT_MINOR_VERSION_ARB, version.1,
0];
let gl_context = create(hdc, handle, attributs.as_ptr());
assert!(wglMakeCurrent(hdc, gl_context).as_bool() == true);
wglDeleteContext(handle);
self.current_handle = Some(gl_context);
return true;
}
}
}
false
}
pub fn get_proc_address(&self, addr: &str) -> *const core::ffi::c_void
{
let addr = CString::new(addr.as_bytes()).unwrap();
let addr = addr.as_ptr();
unsafe
{
let p = wglGetProcAddress(addr) as *const core::ffi::c_void;
if !p.is_null() { return p; }
GetProcAddress(self.opengl32, addr) as *const _
}
}
}
#[repr(C)]
pub struct PIXELFORMATDESCRIPTOR
{
pub nSize: WORD,
pub nVersion: WORD,
pub dwFlags: DWORD,
pub iPixelType: BYTE,
pub cColorBits: BYTE,
pub cRedBits: BYTE,
pub cRedShift: BYTE,
pub cGreenBits: BYTE,
pub cGreenShift: BYTE,
pub cBlueBits: BYTE,
pub cBlueShift: BYTE,
pub cAlphaBits: BYTE,
pub cAlphaShift: BYTE,
pub cAccumBits: BYTE,
pub cAccumRedBits: BYTE,
pub cAccumGreenBits: BYTE,
pub cAccumBlueBits: BYTE,
pub cAccumAlphaBits: BYTE,
pub cDepthBits: BYTE,
pub cStencilBits: BYTE,
pub cAuxBuffers: BYTE,
pub iLayerType: BYTE,
pub bReserved: BYTE,
pub dwLayerMask: DWORD,
pub dwVisibleMask: DWORD,
pub dwDamageMask: DWORD,
}
impl Default for PIXELFORMATDESCRIPTOR
{
#[inline]
fn default() -> Self
{
let mut out: Self = unsafe { core::mem::zeroed() };
out.nSize = core::mem::size_of::<Self>() as WORD;
out.nVersion = 1;
out
}
}
pub const PFD_TYPE_RGBA: u8 = 0;
pub const PFD_TYPE_COLORINDEX: u8 = 1;
pub const PFD_MAIN_PLANE: u8 = 0;
pub const PFD_OVERLAY_PLANE: u8 = 1;
pub const PFD_UNDERLAY_PLANE: u8 = u8::MAX ;
pub const PFD_DOUBLEBUFFER: u32 = 0x00000001;
pub const PFD_STEREO: u32 = 0x00000002;
pub const PFD_DRAW_TO_WINDOW: u32 = 0x00000004;
pub const PFD_DRAW_TO_BITMAP: u32 = 0x00000008;
pub const PFD_SUPPORT_GDI: u32 = 0x00000010;
pub const PFD_SUPPORT_OPENGL: u32 = 0x00000020;
pub const PFD_GENERIC_FORMAT: u32 = 0x00000040;
pub const PFD_NEED_PALETTE: u32 = 0x00000080;
pub const PFD_NEED_SYSTEM_PALETTE: u32 = 0x00000100;
pub const PFD_SWAP_EXCHANGE: u32 = 0x00000200;
pub const PFD_SWAP_COPY: u32 = 0x00000400;
pub const PFD_SWAP_LAYER_BUFFERS: u32 = 0x00000800;
pub const PFD_GENERIC_ACCELERATED: u32 = 0x00001000;
pub const PFD_SUPPORT_DIRECTDRAW: u32 = 0x00002000;
pub const PFD_DIRECT3D_ACCELERATED: u32 = 0x00004000;
pub const PFD_SUPPORT_COMPOSITION: u32 = 0x00008000;
pub const PFD_DEPTH_DONTCARE: u32 = 0x20000000;
pub const PFD_DOUBLEBUFFER_DONTCARE: u32 = 0x40000000;
pub const PFD_STEREO_DONTCARE: u32 = 0x80000000;
type c_char = i8;
type c_int = i32;
type BYTE = u8;
type WORD = c_ushort;
type DWORD = c_ulong;
pub type HGLRC = HANDLE;
pub type LPCSTR = *const c_char;
pub type PROC = *mut core::ffi::c_void;
const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091;
const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092;
#[link(name = "Opengl32")]
extern "system"
{
pub fn wglCreateContext(Arg1: HDC) -> HGLRC;
pub fn wglDeleteContext(Arg1: HGLRC) -> BOOL;
pub fn wglMakeCurrent(hdc: HDC, hglrc: HGLRC) -> BOOL;
pub fn wglGetProcAddress(Arg1: LPCSTR) -> PROC;
}
pub type FARPROC = *mut core::ffi::c_void;
#[link(name = "kernel32", kind = "dylib")]
extern "system"
{
pub fn GetProcAddress(hModule: HMODULE, lpProcName: LPCSTR) -> FARPROC;
}
#[link(name = "Gdi32")]
extern "system"
{
pub fn ChoosePixelFormat(hdc: HDC, ppfd: *const PIXELFORMATDESCRIPTOR) -> c_int;
pub fn SetPixelFormat(hdc: HDC, format: c_int, ppfd: *const PIXELFORMATDESCRIPTOR) -> BOOL;
pub fn SwapBuffers(Arg1: HDC) -> BOOL;
}
type wglCreateContextAttribsARB_t = Option<unsafe extern "system" fn(hDC: HDC, hShareContext: HGLRC, attribList: *const c_int) -> HGLRC>;