fenestroj 0.0.11

Easier wrappers for Win32 API stuff, safe when possible
Documentation
#![cfg(feature = "winuser")]

//! Windows user code, which mostly means Window/GUI stuff.

use super::*;

use winapi::um::winuser::{
  AdjustWindowRect, BeginPaint, ClipCursor, CreateWindowExW, DefWindowProcW,
  DispatchMessageW, EndPaint, GetClientRect, GetCursorPos, GetDC, GetMessageW,
  LoadCursorW, PeekMessageW, PostQuitMessage, RegisterClassW, ReleaseDC,
  ScreenToClient, SetCursorPos, TranslateMessage, PAINTSTRUCT, WNDCLASSW, WS_BORDER,
  WS_CAPTION, WS_CHILD, WS_CHILDWINDOW, WS_CLIPCHILDREN, WS_CLIPSIBLINGS,
  WS_DISABLED, WS_DLGFRAME, WS_GROUP, WS_HSCROLL, WS_ICONIC, WS_MAXIMIZE,
  WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP,
  WS_POPUPWINDOW, WS_SIZEBOX, WS_SYSMENU, WS_TABSTOP, WS_THICKFRAME, WS_TILEDWINDOW,
  WS_VISIBLE, WS_VSCROLL,
};

pub use winapi::um::winuser::{CW_USEDEFAULT, IDC_ARROW, MSG, WM_DESTROY};
// Note(Lokathor): Virtual Key re-exports are given at the end of the file
// because it's like 30 lines even wrapped.

/// A helper for creation of [window
/// style](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles)
/// values.
#[derive(Debug, Clone, Copy, Default)]
pub struct WindowStyle(pub u32);
#[rustfmt::skip]
impl WindowStyle {
  mk_newtype_bitflag32!(WS_BORDER, border, with_border, set_border);
  mk_newtype_bitflag32!(WS_CAPTION, caption, with_caption, set_caption);
  mk_newtype_bitflag32!(WS_CHILD, child, with_child, set_child);
  mk_newtype_bitflag32!(WS_CHILDWINDOW, child_window, with_child_window, set_child_window);
  mk_newtype_bitflag32!(WS_CLIPCHILDREN, clip_children, with_clip_children, set_clip_children);
  mk_newtype_bitflag32!(WS_CLIPSIBLINGS, clip_siblings, with_clip_siblings, set_clip_siblings);
  mk_newtype_bitflag32!(WS_DISABLED, disabled, with_disabled, set_disabled);
  mk_newtype_bitflag32!(WS_DLGFRAME, dlg_frame, with_dlg_frame, set_dlg_frame);
  mk_newtype_bitflag32!(WS_GROUP, group, with_group, set_group);
  mk_newtype_bitflag32!(WS_HSCROLL, h_scroll, with_h_scroll, set_h_scroll);
  mk_newtype_bitflag32!(WS_ICONIC, iconic, with_iconic, set_iconic);
  mk_newtype_bitflag32!(WS_MAXIMIZE, maximize, with_maximize, set_maximize);
  mk_newtype_bitflag32!(WS_MAXIMIZEBOX, maximize_box, with_maximize_box, set_maximize_box);
  mk_newtype_bitflag32!(WS_MINIMIZE, minimize, with_minimize, set_minimize);
  mk_newtype_bitflag32!(WS_MINIMIZEBOX, minimize_box, with_minimize_box, set_minimize_box);
  //mk_newtype_bitflag32!(WS_OVERLAPPED, overlapped, with_overlapped, set_overlapped);
  mk_newtype_bitflag32!(WS_OVERLAPPEDWINDOW, overlapped_window, with_overlapped_window, set_overlapped_window);
  mk_newtype_bitflag32!(WS_POPUP, popup, with_popup, set_popup);
  mk_newtype_bitflag32!(WS_POPUPWINDOW, popup_window, with_popup_window, set_popup_window);
  mk_newtype_bitflag32!(WS_SIZEBOX, size_box, with_size_box, set_size_box);
  mk_newtype_bitflag32!(WS_SYSMENU, sys_menu, with_sys_menu, set_sys_menu);
  mk_newtype_bitflag32!(WS_TABSTOP, tab_stop, with_tab_stop, set_tab_stop);
  mk_newtype_bitflag32!(WS_THICKFRAME, thick_frame, with_thick_frame, set_thick_frame);
  //mk_newtype_bitflag32!(WS_TILED, tiled, with_tiled, set_tiled);
  mk_newtype_bitflag32!(WS_TILEDWINDOW, tiled_window, with_tiled_window, set_tiled_window);
  mk_newtype_bitflag32!(WS_VISIBLE, visible, with_visible, set_visible);
  mk_newtype_bitflag32!(WS_VSCROLL, v_scroll, with_v_scroll, set_v_scroll);
}

/// See [`AdjustWindowRect`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-adjustwindowrect)
#[inline]
pub fn adjust_window_rect(
  rect: &mut EdgeRect,
  style: u32,
  menu: bool,
) -> Result<(), ErrorCode> {
  let output = unsafe {
    AdjustWindowRect(rect as *mut EdgeRect as *mut RECT, style, menu as i32)
  };
  if output != 0 {
    Ok(())
  } else {
    Err(get_last_error())
  }
}

/// See [`BeginPaint`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-beginpaint)
#[inline]
pub unsafe fn begin_paint(hwnd: HWND, paint: &mut PAINTSTRUCT) -> Option<HDC> {
  let hdc = BeginPaint(hwnd, paint);
  if !hdc.is_null() {
    Some(hdc)
  } else {
    None
  }
}

/// Clips the cursor's movement to the screen rectangle specified.
///
/// See [`ClipCursor`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-clipcursor)
#[inline]
pub unsafe fn clip_cursor(rect: &EdgeRect) -> Result<(), ErrorCode> {
  let p: *const RECT = rect as *const _ as *const _;
  let output = ClipCursor(p);
  if output != 0 {
    Ok(())
  } else {
    Err(get_last_error())
  }
}

/// Args for use with [`create_window_ex`](create_window_ex)
#[derive(Debug, Clone, Copy, Default)]
pub struct CreateWindowExRequest<'a> {
  /// Extended style options.
  pub ex_style: u32,
  /// Name of the class that the window will belong to.
  pub class_name: &'a str,
  /// Name of this window.
  pub window_name: &'a str,
  /// Standard style options.
  pub style: WindowStyle,
  /// x position.
  pub x: i32,
  /// y position
  pub y: i32,
  /// width
  pub width: i32,
  /// height
  pub height: i32,
  /// Parent window. `None` means this window has no parent.
  pub parent: Option<HWND>,
  /// Menu handle. `None` makes the window not have a menu.
  pub menu: Option<HMENU>,
  /// Instance that the window should belong to. Pass `None` and it'll
  /// automatically pick the current instance.
  pub instance: Option<HINSTANCE>,
  /// Pointer that gets passed to the callback during the initial set of
  /// window creation messages.
  pub lparam: Option<NonNull<c_void>>,
}

/// This wraps
/// [`CreateWindowExW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw),
/// but you should never ever use the non-W version, so we just drop the `W`
/// entirely.
pub unsafe fn create_window_ex(
  request: CreateWindowExRequest,
) -> Result<HWND, ErrorCode> {
  let class_name_wide_null: Vec<u16> = wide_null(request.class_name);
  let window_name_wide_null: Vec<u16> = wide_null(request.window_name);
  let hwnd = CreateWindowExW(
    request.ex_style,
    class_name_wide_null.as_ptr(),
    window_name_wide_null.as_ptr(),
    request.style.0,
    request.x,
    request.y,
    request.width,
    request.height,
    request.parent.unwrap_or(null_mut()),
    request.menu.unwrap_or(null_mut()),
    request
      .instance
      .unwrap_or_else(|| winapi::um::libloaderapi::GetModuleHandleW(null())),
    core::mem::transmute(request.lparam),
  );
  if hwnd.is_null() {
    Err(get_last_error())
  } else {
    Ok(hwnd)
  }
}

/// Performs default handling of any window procedure message.
///
/// See [`DefWindowProcW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw)
#[inline(always)]
pub unsafe fn default_window_proc(
  win: HWND,
  msg: u32,
  wparam: usize,
  lparam: isize,
) -> isize {
  DefWindowProcW(win, msg, wparam, lparam)
}

/// See [`DispatchMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-dispatchmessagew)
#[inline]
pub unsafe fn dispatch_message(msg: &MSG) -> isize {
  DispatchMessageW(msg)
}

/// See [`EndPaint`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-endpaint)
#[inline]
pub unsafe fn end_paint(hwnd: HWND, paint: &PAINTSTRUCT) {
  EndPaint(hwnd, paint);
}

/// See [`GetClientRect`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect)
#[inline]
pub unsafe fn get_client_rect(
  hwnd: HWND,
  rect: &mut EdgeRect,
) -> Result<(), ErrorCode> {
  if GetClientRect(hwnd, rect as *mut EdgeRect as *mut RECT) != 0 {
    Ok(())
  } else {
    Err(get_last_error())
  }
}

/// See [`GetCursorPos`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos)
#[inline]
pub unsafe fn get_cursor_pos() -> Result<Point, ErrorCode> {
  let mut p = Point { x: 0, y: 0 };
  let result = GetCursorPos(&mut p as *mut Point as *mut POINT);
  if result != 0 {
    Ok(p)
  } else {
    Err(get_last_error())
  }
}

/// See [`GetDC`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc)
#[inline]
pub unsafe fn get_dc(hwnd: HWND) -> Option<HDC> {
  let output = GetDC(hwnd);
  if output.is_null() {
    None
  } else {
    Some(output)
  }
}

/// Returns `Ok(keep_going)` or `Err(code)`.
///
/// * Blocks until a message can be pulled from the message queue and written to
///   the `*&mut MSG` given.
/// * On success, the `bool` value is `true` for any message type other than
///   `WM_QUIT`.
///
/// See
/// [`GetMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew)
#[inline]
pub unsafe fn get_message(
  msg: &mut MSG,
  hwnd: HWND,
  min: u32,
  max: u32,
) -> Result<bool, ErrorCode> {
  let output = GetMessageW(msg, hwnd, min, max);
  match output {
    0 => Ok(false),
    _ if output > 0 => Ok(true),
    _ => Err(get_last_error()),
  }
}

/// Loads a cursor resource.
///
/// This can be used in one of two ways:
/// * To get a cursor out of an executable, pass an instance of the module whose
///   executable file contains the cursor along with the name of the cursor
///   (either as a wide_null string pointer or use `MAKEINTRESOURCE`).
/// * To get a pre-defined cursor pass `null_mut()` as the `HINSTANCE` and then
///   pass the name of a pre-defined cursor. The pre-defined names are given on
///   the MSDN documentation page.
///
/// See
/// [`LoadCursorW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursorw)
pub unsafe fn load_cursor(
  instance: HINSTANCE,
  name: *const u16,
) -> Result<HCURSOR, ErrorCode> {
  let output = LoadCursorW(instance, name);
  if !output.is_null() {
    Ok(output)
  } else {
    Err(get_last_error())
  }
}

/// See [`PeekMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew)
#[inline]
pub unsafe fn peek_message(
  msg: &mut MSG,
  hwnd: HWND,
  min: u32,
  max: u32,
  remove: u32,
) -> bool {
  PeekMessageW(msg, hwnd, min, max, remove) != 0
}

/// See [`PostQuitMessage`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postquitmessage)
#[inline]
pub unsafe fn post_quit_message(exit_code: i32) {
  PostQuitMessage(exit_code)
}

/// Rusty version of [`WNDCLASSW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw)
///
/// Use this with [`register_class`](register_class)
#[allow(missing_debug_implementations)]
#[derive(Clone, Copy, Default)] // TODO(Lokathor): Debug, Default?
pub struct WinClass<'a> {
  /// Style for the class
  pub style: u32,
  /// Window Procedure for this class.
  ///
  /// win, msg, wparam, lparam. See [Window Procedures](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures)
  pub wnd_proc: Option<unsafe extern "system" fn(HWND, u32, usize, isize) -> isize>,
  /// Extra space that the window class should get for you to use.
  pub class_bytes: usize,
  /// Extra class that each window of this class should get for you to use.
  pub window_bytes: usize,
  /// The instance for this window class to be associated with. Use `None` to
  /// have your own instance automatically selected for you.
  pub instance: Option<HINSTANCE>,
  /// Icon for this window class.
  pub icon: Option<HICON>,
  /// Cursor for this window class.
  pub cursor: Option<HCURSOR>,
  /// Background brush for the window class.
  pub background: Option<HBRUSH>,
  /// Name of the menu to use with this class.
  pub menu_name: &'a str,
  /// Name of this window class.
  pub class_name: &'a str,
}

/// See [`RegisterClassW`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassw)
pub unsafe fn register_class(request: WinClass) -> Result<Atom, ErrorCode> {
  let menu_name_wide_null: Vec<u16> = wide_null(request.menu_name);
  let class_name_wide_null: Vec<u16> = wide_null(request.class_name);
  let mut class: WNDCLASSW = core::mem::zeroed();
  class.style = request.style;
  class.lpfnWndProc = request.wnd_proc;
  class.cbClsExtra = i32::try_from(request.class_bytes)
    .map_err(|_| ErrorCode(ErrorCode::APPLICATION_ERROR_BIT))?;
  class.cbWndExtra = i32::try_from(request.window_bytes)
    .map_err(|_| ErrorCode(ErrorCode::APPLICATION_ERROR_BIT))?;
  class.hInstance = match request.instance {
    Some(instance) => instance,
    None => get_module_handle(None)?,
  };
  class.hIcon = request.icon.unwrap_or(null_mut());
  class.hCursor = request.cursor.unwrap_or(null_mut());
  class.hbrBackground = request.background.unwrap_or(null_mut());
  class.lpszMenuName = menu_name_wide_null.as_ptr();
  class.lpszClassName = class_name_wide_null.as_ptr();
  let output = RegisterClassW(&class);
  if output != 0 {
    Ok(Atom(output))
  } else {
    Err(get_last_error())
  }
}

/// See [`ReleaseDC`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc)
#[inline]
pub unsafe fn release_dc(hwnd: HWND, hdc: HDC) -> Result<(), ()> {
  if ReleaseDC(hwnd, hdc) == 1 {
    Ok(())
  } else {
    Err(())
  }
}

/// See [`ScreenToClient`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-screentoclient)
#[inline]
pub unsafe fn screen_to_client(hwnd: HWND, coord: &mut Point) -> Result<(), ()> {
  if ScreenToClient(hwnd, coord as *mut Point as *mut POINT) != 0 {
    Ok(())
  } else {
    Err(())
  }
}

/// See [`SetCursorPos`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setcursorpos)
#[inline]
pub unsafe fn set_cursor_pos(x: i32, y: i32) -> Result<(), ErrorCode> {
  if SetCursorPos(x, y) != 0 {
    Ok(())
  } else {
    Err(get_last_error())
  }
}

/// See [`TranslateMessage`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-translatemessage)
#[inline]
pub unsafe fn translate_message(msg: &MSG) -> bool {
  TranslateMessage(msg) != 0
}

pub use winapi::um::winuser::{
  VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK,
  VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
  VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL,
  VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF,
  VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14,
  VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23,
  VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A,
  VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT,
  VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON,
  VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT,
  VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP,
  VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER,
  VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN,
  VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT,
  VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW,
  VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGEUL, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME,
  VK_ICO_00, VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI,
  VK_LAUNCH_APP1, VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT,
  VK_LBUTTON, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON,
  VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP,
  VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL,
  VK_NAVIGATION_DOWN, VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT,
  VK_NAVIGATION_UP, VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT,
  VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4,
  VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102,
  VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN,
  VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB, VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY,
  VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH, VK_OEM_FJ_JISHO, VK_OEM_FJ_LOYA,
  VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP, VK_OEM_MINUS,
  VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, VK_OEM_PLUS,
  VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, VK_PRINT,
  VK_PRIOR, VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU,
  VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP,
  VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE,
  VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM,
};