core: windows: refactor everything

This commit is contained in:
Jonathan Marler 2025-01-19 12:04:07 -07:00 committed by Emi Gutekanst
parent 1e2cbc4d71
commit f9e1a9087f
5 changed files with 821 additions and 203 deletions

View file

@ -402,6 +402,7 @@ fn buildExamples(
.root_source_file = b.path(b.fmt("examples/{s}/main.zig", .{example.name})),
.target = target,
.optimize = optimize,
.win32_manifest = b.path("src/core/windows/win32.manifest"),
});
exe.root_module.addImport("mach", mach_mod);

View file

@ -3,6 +3,8 @@ const w = @import("../win32.zig");
const mach = @import("../main.zig");
const Core = @import("../Core.zig");
const windowmsg = @import("windowmsg.zig");
const gpu = mach.gpu;
const Event = Core.Event;
const KeyEvent = Core.KeyEvent;
@ -17,20 +19,19 @@ const Position = Core.Position;
const Key = Core.Key;
const KeyMods = Core.KeyMods;
const log = std.log.scoped(.mach);
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
const Win32 = @This();
pub const Native = struct {
window: w.HWND = undefined,
surrogate: u16 = 0,
dinput: *w.IDirectInput8W = undefined,
saved_window_rect: w.RECT = undefined,
surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND = undefined,
const window_ex_style: w.WINDOW_EX_STYLE = .{
.APPWINDOW = 1,
.NOREDIRECTIONBITMAP = 1,
};
pub const Context = struct {
core: *Core,
window_id: mach.ObjectID,
pub const Native = struct {
hwnd: w.HWND,
surrogate: u16 = 0,
// dinput: *w.IDirectInput8W = undefined,
};
pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@TypeOf(on_each_update_fn))) void {
@ -38,140 +39,250 @@ pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@
}
pub fn tick(core: *Core) !void {
var windows = core.windows.slice();
while (windows.next()) |window_id| {
const native_opt: ?Native = core.windows.get(window_id, .native);
if (native_opt) |native| {
_ = native; // autofix
var msg: w.MSG = undefined;
while (w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE) != 0) {
_ = w.TranslateMessage(&msg);
_ = w.DispatchMessageW(&msg);
{
var windows = core.windows.slice();
while (windows.next()) |window_id| {
if (core.windows.get(window_id, .native) != null) {
// TODO: propagate window.decorated and all others
// Handle resizing the window when the user changes width or height
if (core.windows.updated(window_id, .width) or core.windows.updated(window_id, .height)) {
setWindowSize(
core.windows.get(window_id, .native).?.hwnd,
.{
.width = core.windows.get(window_id, .width),
.height = core.windows.get(window_id, .height),
},
);
}
} else {
try initWindow(core, window_id);
std.debug.assert(core.windows.getValue(window_id).native != null);
}
// Handle resizing the window when the user changes width or height
if (core.windows.updated(window_id, .width) or core.windows.updated(window_id, .height)) {}
} else {
try initWindow(core, window_id);
}
}
var msg: w.MSG = undefined;
while (true) {
const result = w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE);
if (result < 0) fatalWin32("PeekMessage", w.GetLastError());
if (result == 0) break;
if (msg.message == w.WM_QUIT) {
std.log.info("quit (exit code {})", .{msg.wParam});
w.ExitProcess(std.math.cast(u32, msg.wParam) orelse 0xffffffff);
}
_ = w.TranslateMessage(&msg);
_ = w.DispatchMessageW(&msg);
}
}
fn setWindowSize(hwnd: w.HWND, size_pt: Size) void {
const dpi = w.dpiFromHwnd(hwnd);
const style = styleFromHwnd(hwnd);
var rect: w.RECT = .{
.left = 0,
.top = 0,
.right = w.pxFromPt(i32, @intCast(size_pt.width), dpi),
.bottom = w.pxFromPt(i32, @intCast(size_pt.height), dpi),
};
if (0 == w.AdjustWindowRectExForDpi(&rect, style, w.FALSE, window_ex_style, dpi)) fatalWin32(
"AdjustWindowRectExForDpi",
w.GetLastError(),
);
if (0 == w.SetWindowPos(
hwnd,
null,
undefined,
undefined,
rect.right - rect.left,
rect.bottom - rect.top,
.{ .NOZORDER = 1, .NOMOVE = 1 },
)) fatalWin32("SetWindowPos", w.GetLastError());
}
fn updateWindowSize(
dpi: u32,
window_style: w.WINDOW_STYLE,
hwnd: w.HWND,
requested_client_size: w.SIZE,
) void {
const monitor = blk: {
var rect: w.RECT = undefined;
if (0 == w.GetWindowRect(hwnd, &rect)) fatalWin32("GetWindowRect", w.GetLastError());
break :blk w.MonitorFromPoint(
.{ .x = rect.left, .y = rect.top },
w.MONITOR_DEFAULTTONULL,
) orelse {
log.warn("MonitorFromPoint {},{} failed with {}", .{ rect.left, rect.top, w.GetLastError() });
return;
};
};
const work_rect: w.RECT = blk: {
var info: w.MONITORINFO = undefined;
info.cbSize = @sizeOf(w.MONITORINFO);
if (0 == w.GetMonitorInfoW(monitor, &info)) {
log.warn("GetMonitorInfo failed with {}", .{w.GetLastError()});
return;
}
break :blk info.rcWork;
};
const work_size: w.SIZE = .{
.cx = work_rect.right - work_rect.left,
.cy = work_rect.bottom - work_rect.top,
};
log.debug(
"primary monitor work topleft={},{} size={}x{}",
.{ work_rect.left, work_rect.top, work_size.cx, work_size.cy },
);
const wanted_size: w.SIZE = blk: {
var rect: w.RECT = .{
.left = 0,
.top = 0,
.right = requested_client_size.cx,
.bottom = requested_client_size.cy,
};
if (0 == w.AdjustWindowRectExForDpi(&rect, window_style, w.FALSE, window_ex_style, dpi)) fatalWin32(
"AdjustWindowRectExForDpi",
w.GetLastError(),
);
break :blk .{
.cx = rect.right - rect.left,
.cy = rect.bottom - rect.top,
};
};
const window_size: w.SIZE = .{
.cx = @min(wanted_size.cx, work_size.cx),
.cy = @min(wanted_size.cy, work_size.cy),
};
if (0 == w.SetWindowPos(
hwnd,
null,
work_rect.left + @divTrunc(work_size.cx - window_size.cx, 2),
work_rect.top + @divTrunc(work_size.cy - window_size.cy, 2),
window_size.cx,
window_size.cy,
.{ .NOZORDER = 1 },
)) fatalWin32("SetWindowPos", w.GetLastError());
}
const CreateWindowArgs = struct {
window_id: mach.ObjectID,
};
var wndproc_core: *Core = undefined;
fn initWindow(
core: *Core,
window_id: mach.ObjectID,
) !void {
wndproc_core = core;
var core_window = core.windows.getValue(window_id);
const hInstance = w.GetModuleHandleW(null);
const class_name = w.L("mach");
const class = std.mem.zeroInit(w.WNDCLASSW, .{
.style = w.CS_OWNDC,
.lpfnWndProc = wndProc,
.hInstance = hInstance,
.hIcon = w.LoadIconW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDI_APPLICATION)))),
.hCursor = w.LoadCursorW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDC_ARROW)))),
.lpszClassName = class_name,
});
if (w.RegisterClassW(&class) == 0) return error.Unexpected;
{
const class: w.WNDCLASSW = .{
.style = .{},
.lpfnWndProc = wndProc,
.cbClsExtra = 0,
.cbWndExtra = @sizeOf(mach.ObjectID),
.hInstance = hInstance,
.hIcon = w.LoadIconW(null, w.IDI_APPLICATION),
.hCursor = w.LoadCursorW(null, w.IDC_ARROW),
.hbrBackground = null,
.lpszMenuName = null,
.lpszClassName = class_name,
};
if (w.RegisterClassW(&class) == 0) fatalWin32("RegisterClass", w.GetLastError());
}
const title = try std.unicode.utf8ToUtf16LeAllocZ(core.allocator, core_window.title);
defer core.allocator.free(title);
var request_window_width: i32 = @bitCast(core_window.width);
var request_window_height: i32 = @bitCast(core_window.height);
const window_ex_style: w.WINDOW_EX_STYLE = .{ .APPWINDOW = 1 };
const window_style: w.WINDOW_STYLE = if (core_window.decorated) w.WS_OVERLAPPEDWINDOW else w.WS_POPUPWINDOW; // w.WINDOW_STYLE{.POPUP = 1};
var rect: w.RECT = .{ .left = 0, .top = 0, .right = request_window_width, .bottom = request_window_height };
if (w.TRUE == w.AdjustWindowRectEx(&rect, window_style, w.FALSE, window_ex_style)) {
request_window_width = rect.right - rect.left;
request_window_height = rect.bottom - rect.top;
}
const native_window = w.CreateWindowExW(
const create_args: CreateWindowArgs = .{
.window_id = window_id,
};
const hwnd = w.CreateWindowExW(
window_ex_style,
class_name,
title,
window_style,
w.CW_USEDEFAULT,
w.CW_USEDEFAULT,
request_window_width,
request_window_height,
w.CW_USEDEFAULT,
w.CW_USEDEFAULT,
null,
null,
hInstance,
null,
@constCast(@ptrCast(&create_args)),
) orelse return error.Unexpected;
var native: Native = .{};
const dpi = w.dpiFromHwnd(hwnd);
var dinput: ?*w.IDirectInput8W = undefined;
const ptr: ?*?*anyopaque = @ptrCast(&dinput);
if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) {
return error.Unexpected;
}
native.dinput = dinput.?;
updateWindowSize(dpi, window_style, hwnd, .{
.cx = @bitCast(core_window.width),
.cy = @bitCast(core_window.height),
});
native.surface_descriptor_from_hwnd = .{
.hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?,
.hwnd = native_window,
};
// const dinput = blk: {
// var dinput: ?*w.IDirectInput8W = undefined;
// const ptr: ?*?*anyopaque = @ptrCast(&dinput);
// if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) {
// return error.Unexpected;
// }
// break :blk dinput;
// };
core_window.surface_descriptor = .{ .next_in_chain = .{
.from_windows_hwnd = &native.surface_descriptor_from_hwnd,
} };
const context = try core.allocator.create(Context);
context.* = .{ .core = core, .window_id = window_id };
_ = w.SetWindowLongPtrW(native_window, w.GWLP_USERDATA, @bitCast(@intFromPtr(context)));
restoreWindowPosition(core, window_id);
const size = getClientRect(core, window_id);
const size = getClientSize(hwnd);
core_window.width = size.width;
core_window.height = size.height;
_ = w.GetWindowRect(native.window, &native.saved_window_rect);
_ = w.ShowWindow(hwnd, w.SW_SHOW);
core_window.native = native;
core.windows.setValueRaw(window_id, core_window);
try core.initWindow(window_id);
_ = w.ShowWindow(native_window, w.SW_SHOW);
// try some things to bring our window to the top
const HWND_TOP: ?w.HWND = null;
_ = w.SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, .{ .NOMOVE = 1, .NOSIZE = 1 });
_ = w.SetForegroundWindow(hwnd);
_ = w.BringWindowToTop(hwnd);
{
// TODO: make this lifetime better
var surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND = .{
.hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?,
.hwnd = hwnd,
};
core.windows.setRaw(window_id, .surface_descriptor, .{ .next_in_chain = .{
.from_windows_hwnd = &surface_descriptor_from_hwnd,
} });
try core.initWindow(window_id);
}
}
fn windowIdFromHwnd(hwnd: w.HWND) mach.ObjectID {
const userdata: usize = @bitCast(w.GetWindowLongPtrW(hwnd, @enumFromInt(0)));
if (userdata == 0) unreachable;
return @bitCast(userdata - 1);
}
fn styleFromHwnd(hwnd: w.HWND) w.WINDOW_STYLE {
return @bitCast(@as(u32, @truncate(@as(usize, @bitCast(w.GetWindowLongPtrW(hwnd, w.GWL_EXSTYLE))))));
}
// -----------------------------
// Internal functions
// -----------------------------
fn getClientRect(core: *Core, window_id: mach.ObjectID) Size {
const window = core.windows.getValue(window_id);
if (window.native) |native| {
var rect: w.RECT = undefined;
_ = w.GetClientRect(native.window, &rect);
const width: u32 = @intCast(rect.right - rect.left);
const height: u32 = @intCast(rect.bottom - rect.top);
return .{ .width = width, .height = height };
}
return .{ .width = 0, .height = 0 };
}
fn restoreWindowPosition(core: *Core, window_id: mach.ObjectID) void {
const window = core.windows.getValue(window_id);
if (window.native) |native| {
if (native.saved_window_rect.right - native.saved_window_rect.left == 0) {
_ = w.ShowWindow(native.window, w.SW_RESTORE);
} else {
_ = w.SetWindowPos(native.window, null, native.saved_window_rect.left, native.saved_window_rect.top, native.saved_window_rect.right - native.saved_window_rect.left, native.saved_window_rect.bottom - native.saved_window_rect.top, w.SWP_SHOWWINDOW);
}
}
fn getClientSize(hwnd: w.HWND) Size {
var rect: w.RECT = undefined;
if (0 == w.GetClientRect(hwnd, &rect))
fatalWin32("GetClientRect", w.GetLastError());
std.debug.assert(rect.left == 0);
std.debug.assert(rect.top == 0);
return .{ .width = @intCast(rect.right), .height = @intCast(rect.bottom) };
}
fn getKeyboardModifiers() mach.Core.KeyMods {
@ -186,52 +297,79 @@ fn getKeyboardModifiers() mach.Core.KeyMods {
};
}
fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT {
const context = blk: {
const userdata: usize = @bitCast(w.GetWindowLongPtrW(wnd, w.GWLP_USERDATA));
const ptr: ?*Context = @ptrFromInt(userdata);
break :blk ptr orelse return w.DefWindowProcW(wnd, msg, wParam, lParam);
const debug_wndproc_log = false;
var global_msg_tail: ?*windowmsg.MessageNode = null;
fn wndProc(hwnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT {
var msg_node: windowmsg.MessageNode = undefined;
if (debug_wndproc_log) msg_node.init(&global_msg_tail, hwnd, msg, wParam, lParam);
defer if (debug_wndproc_log) msg_node.deinit();
if (debug_wndproc_log) switch (msg) {
w.WM_MOUSEMOVE => {},
else => std.log.info("{}", .{msg_node.fmtPath()}),
};
const core = context.core;
const window_id = context.window_id;
var core_window = core.windows.getValue(window_id);
const core = wndproc_core;
switch (msg) {
w.WM_CLOSE => {
core.pushEvent(.{ .close = .{ .window_id = window_id } });
w.WM_CREATE => {
const create_struct: *w.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lParam)));
const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams));
const window_id = create_args.window_id;
core.windows.setRaw(window_id, .native, .{ .hwnd = hwnd });
// we add 1 to distinguish between a valid window id and an uninitialized slot
std.debug.assert(0 == w.SetWindowLongPtrW(hwnd, @enumFromInt(0), @bitCast(create_args.window_id + 1)));
std.debug.assert(create_args.window_id == windowIdFromHwnd(hwnd));
return 0;
},
w.WM_SIZE => {
const width: u32 = @as(u32, @intCast(lParam & 0xFFFF));
const height: u32 = @as(u32, @intCast((lParam >> 16) & 0xFFFF));
w.WM_DESTROY => @panic("Mach doesn't support destroying windows yet"),
w.WM_CLOSE => {
core.pushEvent(.{ .close = .{ .window_id = windowIdFromHwnd(hwnd) } });
return 0;
},
w.WM_DPICHANGED, w.WM_WINDOWPOSCHANGED => {
const client_size_px = getClientSize(hwnd);
if (core_window.width != width or core_window.height != height) {
const window_id = windowIdFromHwnd(hwnd);
var core_window = core.windows.getValue(window_id);
var change = false;
if (core_window.framebuffer_width != client_size_px.width or core_window.framebuffer_height != client_size_px.height) {
change = true;
// Recreate the swap_chain
core_window.swap_chain.release();
core_window.swap_chain_descriptor.width = width;
core_window.swap_chain_descriptor.height = height;
core_window.swap_chain_descriptor.width = client_size_px.width;
core_window.swap_chain_descriptor.height = client_size_px.height;
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
core_window.width = width;
core_window.height = height;
core_window.framebuffer_width = width;
core_window.framebuffer_height = height;
core.pushEvent(.{ .window_resize = .{ .window_id = window_id, .size = .{ .width = width, .height = height } } });
core.windows.setValueRaw(window_id, core_window);
core_window.framebuffer_width = client_size_px.width;
core_window.framebuffer_height = client_size_px.height;
}
// TODO (win32): only send resize event when sizing is done.
// the main mach loops does not run while resizing.
// Which means if events are pushed here they will
// queue up until resize is done.
const dpi = w.dpiFromHwnd(hwnd);
const client_size_pt: Size = .{
.width = w.ptFromPx(u32, client_size_px.width, dpi),
.height = w.ptFromPx(u32, client_size_px.height, dpi),
};
if (core_window.width != client_size_pt.width or core_window.height != client_size_pt.height) {
change = true;
core_window.width = client_size_pt.width;
core_window.height = client_size_pt.height;
}
if (change) {
core.pushEvent(.{ .window_resize = .{
.window_id = window_id,
.size = .{ .width = client_size_pt.width, .height = client_size_pt.height },
} });
core.windows.setValueRaw(window_id, core_window);
}
return 0;
},
w.WM_KEYDOWN, w.WM_KEYUP, w.WM_SYSKEYDOWN, w.WM_SYSKEYUP => {
// ScanCode: Unique Identifier for a physical button.
// Virtulkey: A key with a name, multiple physical buttons can produce the same virtual key.
const window_id = windowIdFromHwnd(hwnd);
const vkey: w.VIRTUAL_KEY = @enumFromInt(wParam);
if (vkey == w.VK_PROCESSKEY) return 0;
@ -241,43 +379,47 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
return 0;
}
const flags = lParam >> 16;
const scancode: u9 = @intCast(flags & 0x1FF);
const WinKeyFlags = packed struct(u32) {
repeat_count: u16,
scancode: u8,
extended: bool,
reserved: u4,
context: bool,
previous: bool,
transition: bool,
};
const flags: WinKeyFlags = @bitCast(@as(u32, @truncate(@as(usize, @bitCast(lParam)))));
const scancode: u9 = flags.scancode | (@as(u9, if (flags.extended) 1 else 0) << 8);
if (scancode == 0x1D) {
// right alt sends left control first
var next: w.MSG = undefined;
const time = w.GetMessageTime();
if (core_window.native) |native| {
if (w.PeekMessageW(&next, native.window, 0, 0, w.PM_NOREMOVE) != 0 and
next.time == time and
(next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and
((next.lParam >> 16) & 0x1FF) == 0x138)
{
return 0;
}
if (w.PeekMessageW(&next, hwnd, 0, 0, w.PM_NOREMOVE) != 0 and
next.time == time and
(next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and
((next.lParam >> 16) & 0x1FF) == 0x138)
{
return 0;
}
}
const mods = getKeyboardModifiers();
const key = keyFromScancode(scancode);
if (msg == w.WM_KEYDOWN or msg == w.WM_SYSKEYDOWN) {
if (flags & w.KF_REPEAT == 0)
core.pushEvent(.{
.key_press = .{
.window_id = window_id,
.key = key,
.mods = mods,
},
})
else
core.pushEvent(.{
.key_repeat = .{
.window_id = window_id,
.key = key,
.mods = mods,
},
});
if (flags.previous) core.pushEvent(.{
.key_repeat = .{
.window_id = window_id,
.key = key,
.mods = mods,
},
}) else core.pushEvent(.{
.key_press = .{
.window_id = window_id,
.key = key,
.mods = mods,
},
});
} else core.pushEvent(.{
.key_release = .{
.window_id = window_id,
@ -289,29 +431,32 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
return 0;
},
w.WM_CHAR => {
if (core_window.native) |*native| {
const char: u16 = @truncate(wParam);
var chars: []const u16 = undefined;
if (native.surrogate != 0) {
chars = &.{ native.surrogate, char };
native.surrogate = 0;
} else if (std.unicode.utf16IsHighSurrogate(char)) {
native.surrogate = char;
const window_id = windowIdFromHwnd(hwnd);
const chars: [2]u16 = blk: {
var native = core.windows.get(window_id, .native).?;
defer core.windows.setRaw(window_id, .native, native);
const chars = [2]u16{ native.surrogate, @truncate(wParam) };
if (std.unicode.utf16IsHighSurrogate(chars[1])) {
native.surrogate = chars[1];
return 0;
} else {
chars = &.{char};
}
var iter = std.unicode.Utf16LeIterator.init(chars);
if (iter.nextCodepoint()) |codepoint| {
core.pushEvent(.{ .char_input = .{
.window_id = window_id,
.codepoint = codepoint.?,
} });
} else |err| {
err catch {};
native.surrogate = 0;
break :blk chars;
};
const codepoint: u21 = blk: {
if (std.unicode.utf16IsHighSurrogate(chars[0])) {
if (std.unicode.utf16DecodeSurrogatePair(&chars)) |c| break :blk c else |e| switch (e) {
error.ExpectedSecondSurrogateHalf => {},
}
}
return 0;
}
break :blk chars[1];
};
core.pushEvent(.{ .char_input = .{
.window_id = window_id,
.codepoint = codepoint,
} });
return 0;
},
w.WM_LBUTTONDOWN,
w.WM_LBUTTONUP,
@ -322,15 +467,26 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
w.WM_XBUTTONDOWN,
w.WM_XBUTTONUP,
=> {
const window_id = windowIdFromHwnd(hwnd);
const mods = getKeyboardModifiers();
const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF)));
const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF)));
const xbutton: u32 = @truncate(wParam >> 16);
const point = w.pointFromLparam(lParam);
const MouseFlags = packed struct(u8) {
left_down: bool,
right_down: bool,
shift_down: bool,
control_down: bool,
middle_down: bool,
xbutton1_down: bool,
xbutton2_down: bool,
_: bool,
};
const flags: MouseFlags = @bitCast(@as(u8, @truncate(wParam)));
const button: MouseButton = switch (msg) {
w.WM_LBUTTONDOWN, w.WM_LBUTTONUP => .left,
w.WM_RBUTTONDOWN, w.WM_RBUTTONUP => .right,
w.WM_MBUTTONDOWN, w.WM_MBUTTONUP => .middle,
else => if (xbutton == @as(u32, @bitCast(w.XBUTTON1))) .four else .five,
else => if (flags.xbutton1_down) .four else .five,
};
switch (msg) {
@ -343,7 +499,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
.window_id = window_id,
.button = button,
.mods = mods,
.pos = .{ .x = x, .y = y },
.pos = .{ .x = @floatFromInt(point.x), .y = @floatFromInt(point.y) },
},
}),
else => core.pushEvent(.{
@ -351,7 +507,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
.window_id = window_id,
.button = button,
.mods = mods,
.pos = .{ .x = x, .y = y },
.pos = .{ .x = @floatFromInt(point.x), .y = @floatFromInt(point.y) },
},
}),
}
@ -359,20 +515,21 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
return if (msg == w.WM_XBUTTONDOWN or msg == w.WM_XBUTTONUP) w.TRUE else 0;
},
w.WM_MOUSEMOVE => {
const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF)));
const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF)));
const window_id = windowIdFromHwnd(hwnd);
const point = w.pointFromLparam(lParam);
core.pushEvent(.{
.mouse_motion = .{
.window_id = window_id,
.pos = .{
.x = x,
.y = y,
.x = @floatFromInt(point.x),
.y = @floatFromInt(point.y),
},
},
});
return 0;
},
w.WM_MOUSEWHEEL => {
const window_id = windowIdFromHwnd(hwnd);
const WHEEL_DELTA = 120.0;
const wheel_high_word: u16 = @truncate((wParam >> 16) & 0xffff);
const delta_y: f32 = @as(f32, @floatFromInt(@as(i16, @bitCast(wheel_high_word)))) / WHEEL_DELTA;
@ -387,17 +544,17 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
return 0;
},
w.WM_SETFOCUS => {
const window_id = windowIdFromHwnd(hwnd);
core.pushEvent(.{ .focus_gained = .{ .window_id = window_id } });
return 0;
},
w.WM_KILLFOCUS => {
const window_id = windowIdFromHwnd(hwnd);
core.pushEvent(.{ .focus_lost = .{ .window_id = window_id } });
return 0;
},
else => return w.DefWindowProcW(wnd, msg, wParam, lParam),
else => return w.DefWindowProcW(hwnd, msg, wParam, lParam),
}
return w.DefWindowProcW(wnd, msg, wParam, lParam);
}
fn keyFromScancode(scancode: u9) Key {
@ -542,7 +699,10 @@ fn keyFromScancode(scancode: u9) Key {
return if (scancode > 0 and scancode <= table.len) table[scancode - 1] else .unknown;
}
fn fatalWin32(what: []const u8, last_error: u32) noreturn {
std.debug.panic("{s} failed, error={}", .{ what, last_error });
}
// TODO (win32) Implement consistent error handling when interfacing with the Windows API.
// TODO (win32) Support High DPI awareness
// TODO (win32) Consider to add support for mouse capture
// TODO (win32) Change to using WM_INPUT for mouse movement.

349
src/core/windowmsg.zig Normal file
View file

@ -0,0 +1,349 @@
const std = @import("std");
const win32 = @import("../win32.zig");
pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT {
return .{
.x = @as(i16, @bitCast(win32.loword(lparam))),
.y = @as(i16, @bitCast(win32.hiword(lparam))),
};
}
pub const MessageNode = struct {
tail_ref: *?*MessageNode,
hwnd: win32.HWND,
msg: u32,
wparam: win32.WPARAM,
lparam: win32.LPARAM,
old_tail: ?*MessageNode,
pub fn init(
self: *MessageNode,
tail_ref: *?*MessageNode,
hwnd: win32.HWND,
msg: u32,
wparam: win32.WPARAM,
lparam: win32.LPARAM,
) void {
if (tail_ref.*) |old_tail| {
std.debug.assert(old_tail.hwnd == hwnd);
}
self.* = .{
.tail_ref = tail_ref,
.hwnd = hwnd,
.msg = msg,
.wparam = wparam,
.lparam = lparam,
.old_tail = tail_ref.*,
};
tail_ref.* = self;
}
pub fn deinit(self: *MessageNode) void {
std.debug.assert(self.tail_ref.* == self);
self.tail_ref.* = self.old_tail;
}
pub fn fmtPath(self: *MessageNode) FmtPath {
return .{ .node = self };
}
};
fn writeMessageNodePath(
writer: anytype,
node: *MessageNode,
) !void {
if (node.old_tail) |old_tail| {
try writeMessageNodePath(writer, old_tail);
try writer.writeAll(" > ");
}
try writer.print("{s}:{}", .{ msg_name(node.msg) orelse "?", node.msg });
switch (node.msg) {
win32.WM_CAPTURECHANGED => {
try writer.print("({})", .{node.lparam});
},
win32.WM_SYSCOMMAND => {
try writer.print("(type=0x{x})", .{0xfff0 & node.wparam});
},
else => {},
}
}
const FmtPath = struct {
node: *MessageNode,
const Self = @This();
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = fmt;
_ = options;
try writeMessageNodePath(writer, self.node);
}
};
pub fn msg_name(msg: u32) ?[]const u8 {
return switch (msg) {
0 => "WM_NULL",
1 => "WM_CREATE",
2 => "WM_DESTROY",
3 => "WM_MOVE",
5 => "WM_SIZE",
6 => "WM_ACTIVATE",
7 => "WM_SETFOCUS",
8 => "WM_KILLFOCUS",
10 => "WM_ENABLE",
11 => "WM_SETREDRAW",
12 => "WM_SETTEXT",
13 => "WM_GETTEXT",
14 => "WM_GETTEXTLENGTH",
15 => "WM_PAINT",
16 => "WM_CLOSE",
17 => "WM_QUERYENDSESSION",
18 => "WM_QUIT",
19 => "WM_QUERYOPEN",
20 => "WM_ERASEBKGND",
21 => "WM_SYSCOLORCHANGE",
22 => "WM_ENDSESSION",
24 => "WM_SHOWWINDOW",
25 => "WM_CTLCOLOR",
26 => "WM_WININICHANGE",
27 => "WM_DEVMODECHANGE",
28 => "WM_ACTIVATEAPP",
29 => "WM_FONTCHANGE",
30 => "WM_TIMECHANGE",
31 => "WM_CANCELMODE",
32 => "WM_SETCURSOR",
33 => "WM_MOUSEACTIVATE",
34 => "WM_CHILDACTIVATE",
35 => "WM_QUEUESYNC",
36 => "WM_GETMINMAXINFO",
38 => "WM_PAINTICON",
39 => "WM_ICONERASEBKGND",
40 => "WM_NEXTDLGCTL",
42 => "WM_SPOOLERSTATUS",
43 => "WM_DRAWITEM",
44 => "WM_MEASUREITEM",
45 => "WM_DELETEITEM",
46 => "WM_VKEYTOITEM",
47 => "WM_CHARTOITEM",
48 => "WM_SETFONT",
49 => "WM_GETFONT",
50 => "WM_SETHOTKEY",
51 => "WM_GETHOTKEY",
55 => "WM_QUERYDRAGICON",
57 => "WM_COMPAREITEM",
61 => "WM_GETOBJECT",
65 => "WM_COMPACTING",
68 => "WM_COMMNOTIFY",
70 => "WM_WINDOWPOSCHANGING",
71 => "WM_WINDOWPOSCHANGED",
72 => "WM_POWER",
73 => "WM_COPYGLOBALDATA",
74 => "WM_COPYDATA",
75 => "WM_CANCELJOURNAL",
78 => "WM_NOTIFY",
80 => "WM_INPUTLANGCHANGEREQUEST",
81 => "WM_INPUTLANGCHANGE",
82 => "WM_TCARD",
83 => "WM_HELP",
84 => "WM_USERCHANGED",
85 => "WM_NOTIFYFORMAT",
123 => "WM_CONTEXTMENU",
124 => "WM_STYLECHANGING",
125 => "WM_STYLECHANGED",
126 => "WM_DISPLAYCHANGE",
127 => "WM_GETICON",
128 => "WM_SETICON",
129 => "WM_NCCREATE",
130 => "WM_NCDESTROY",
131 => "WM_NCCALCSIZE",
132 => "WM_NCHITTEST",
133 => "WM_NCPAINT",
134 => "WM_NCACTIVATE",
135 => "WM_GETDLGCODE",
136 => "WM_SYNCPAINT",
160 => "WM_NCMOUSEMOVE",
161 => "WM_NCLBUTTONDOWN",
162 => "WM_NCLBUTTONUP",
163 => "WM_NCLBUTTONDBLCLK",
164 => "WM_NCRBUTTONDOWN",
165 => "WM_NCRBUTTONUP",
166 => "WM_NCRBUTTONDBLCLK",
167 => "WM_NCMBUTTONDOWN",
168 => "WM_NCMBUTTONUP",
169 => "WM_NCMBUTTONDBLCLK",
171 => "WM_NCXBUTTONDOWN",
172 => "WM_NCXBUTTONUP",
173 => "WM_NCXBUTTONDBLCLK",
255 => "WM_INPUT",
256 => "WM_KEYDOWN",
257 => "WM_KEYUP",
258 => "WM_CHAR",
259 => "WM_DEADCHAR",
260 => "WM_SYSKEYDOWN",
261 => "WM_SYSKEYUP",
262 => "WM_SYSCHAR",
263 => "WM_SYSDEADCHAR",
265 => "WM_UNICHAR",
266 => "WM_CONVERTREQUEST",
267 => "WM_CONVERTRESULT",
268 => "WM_INTERIM",
269 => "WM_IME_STARTCOMPOSITION",
270 => "WM_IME_ENDCOMPOSITION",
271 => "WM_IME_COMPOSITION",
272 => "WM_INITDIALOG",
273 => "WM_COMMAND",
274 => "WM_SYSCOMMAND",
275 => "WM_TIMER",
276 => "WM_HSCROLL",
277 => "WM_VSCROLL",
278 => "WM_INITMENU",
279 => "WM_INITMENUPOPUP",
280 => "WM_SYSTIMER",
287 => "WM_MENUSELECT",
288 => "WM_MENUCHAR",
289 => "WM_ENTERIDLE",
290 => "WM_MENURBUTTONUP",
291 => "WM_MENUDRAG",
292 => "WM_MENUGETOBJECT",
293 => "WM_UNINITMENUPOPUP",
294 => "WM_MENUCOMMAND",
295 => "WM_CHANGEUISTATE",
296 => "WM_UPDATEUISTATE",
297 => "WM_QUERYUISTATE",
305 => "WM_LBTRACKPOINT",
306 => "WM_CTLCOLORMSGBOX",
307 => "WM_CTLCOLOREDIT",
308 => "WM_CTLCOLORLISTBOX",
309 => "WM_CTLCOLORBTN",
310 => "WM_CTLCOLORDLG",
311 => "WM_CTLCOLORSCROLLBAR",
312 => "WM_CTLCOLORSTATIC",
512 => "WM_MOUSEMOVE",
513 => "WM_LBUTTONDOWN",
514 => "WM_LBUTTONUP",
515 => "WM_LBUTTONDBLCLK",
516 => "WM_RBUTTONDOWN",
517 => "WM_RBUTTONUP",
518 => "WM_RBUTTONDBLCLK",
519 => "WM_MBUTTONDOWN",
520 => "WM_MBUTTONUP",
521 => "WM_MBUTTONDBLCLK",
522 => "WM_MOUSEWHEEL",
523 => "WM_XBUTTONDOWN",
524 => "WM_XBUTTONUP",
525 => "WM_XBUTTONDBLCLK",
526 => "WM_MOUSEHWHEEL",
528 => "WM_PARENTNOTIFY",
529 => "WM_ENTERMENULOOP",
530 => "WM_EXITMENULOOP",
531 => "WM_NEXTMENU",
532 => "WM_SIZING",
533 => "WM_CAPTURECHANGED",
534 => "WM_MOVING",
536 => "WM_POWERBROADCAST",
537 => "WM_DEVICECHANGE",
544 => "WM_MDICREATE",
545 => "WM_MDIDESTROY",
546 => "WM_MDIACTIVATE",
547 => "WM_MDIRESTORE",
548 => "WM_MDINEXT",
549 => "WM_MDIMAXIMIZE",
550 => "WM_MDITILE",
551 => "WM_MDICASCADE",
552 => "WM_MDIICONARRANGE",
553 => "WM_MDIGETACTIVE",
560 => "WM_MDISETMENU",
561 => "WM_ENTERSIZEMOVE",
562 => "WM_EXITSIZEMOVE",
563 => "WM_DROPFILES",
564 => "WM_MDIREFRESHMENU",
640 => "WM_IME_REPORT",
641 => "WM_IME_SETCONTEXT",
642 => "WM_IME_NOTIFY",
643 => "WM_IME_CONTROL",
644 => "WM_IME_COMPOSITIONFULL",
645 => "WM_IME_SELECT",
646 => "WM_IME_CHAR",
648 => "WM_IME_REQUEST",
656 => "WM_IME_KEYDOWN",
657 => "WM_IME_KEYUP",
672 => "WM_NCMOUSEHOVER",
673 => "WM_MOUSEHOVER",
674 => "WM_NCMOUSELEAVE",
675 => "WM_MOUSELEAVE",
768 => "WM_CUT",
769 => "WM_COPY",
770 => "WM_PASTE",
771 => "WM_CLEAR",
772 => "WM_UNDO",
773 => "WM_RENDERFORMAT",
774 => "WM_RENDERALLFORMATS",
775 => "WM_DESTROYCLIPBOARD",
776 => "WM_DRAWCLIPBOARD",
777 => "WM_PAINTCLIPBOARD",
778 => "WM_VSCROLLCLIPBOARD",
779 => "WM_SIZECLIPBOARD",
780 => "WM_ASKCBFORMATNAME",
781 => "WM_CHANGECBCHAIN",
782 => "WM_HSCROLLCLIPBOARD",
783 => "WM_QUERYNEWPALETTE",
784 => "WM_PALETTEISCHANGING",
785 => "WM_PALETTECHANGED",
786 => "WM_HOTKEY",
791 => "WM_PRINT",
792 => "WM_PRINTCLIENT",
793 => "WM_APPCOMMAND",
799 => "WM_DWMNCRENDERINGCHANGED",
856 => "WM_HANDHELDFIRST",
863 => "WM_HANDHELDLAST",
864 => "WM_AFXFIRST",
895 => "WM_AFXLAST",
896 => "WM_PENWINFIRST",
897 => "WM_RCRESULT",
898 => "WM_HOOKRCRESULT",
899 => "WM_GLOBALRCCHANGE",
900 => "WM_SKB",
901 => "WM_PENCTL",
902 => "WM_PENMISC",
903 => "WM_CTLINIT",
904 => "WM_PENEVENT",
911 => "WM_PENWINLAST",
1024 => "WM_USER+0",
1025 => "WM_USER+1",
1026 => "WM_USER+2",
1027 => "WM_USER+3",
1028 => "WM_USER+4",
1029 => "WM_USER+5",
1030 => "WM_USER+6",
else => null,
};
}
pub fn getHitName(hit: win32.LRESULT) ?[]const u8 {
return switch (hit) {
win32.HTERROR => "err",
win32.HTTRANSPARENT => "transprnt",
win32.HTNOWHERE => "nowhere",
win32.HTCLIENT => "client",
win32.HTCAPTION => "caption",
win32.HTSYSMENU => "sysmnu",
win32.HTSIZE => "size",
win32.HTMENU => "menu",
win32.HTHSCROLL => "hscroll",
win32.HTVSCROLL => "vscroll",
win32.HTMINBUTTON => "minbtn",
win32.HTMAXBUTTON => "max",
win32.HTLEFT => "left",
win32.HTRIGHT => "right",
win32.HTTOP => "top",
win32.HTTOPLEFT => "topleft",
win32.HTTOPRIGHT => "topright",
win32.HTBOTTOM => "bottom",
win32.HTBOTTOMLEFT => "botmleft",
win32.HTBOTTOMRIGHT => "botmright",
win32.HTBORDER => "border",
win32.HTCLOSE => "close",
win32.HTHELP => "help",
else => null,
};
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>

View file

@ -27,6 +27,13 @@ pub const WINAPI = w.WINAPI;
pub const TRUE = w.TRUE;
pub const FALSE = w.FALSE;
pub const SIZE = extern struct {
cx: i32,
cy: i32,
};
pub extern "kernel32" fn GetLastError() callconv(w.WINAPI) u32;
//pub const GetModuleHandleW = w.kernel32.GetModuleHandleW;
// TODO: this type is limited to platform 'windows5.1.2600'
pub extern "kernel32" fn GetModuleHandleW(
@ -99,19 +106,12 @@ pub extern "user32" fn GetWindowRect(
lpRect: ?*RECT,
) callconv(@import("std").os.windows.WINAPI) BOOL;
// TODO: this type is limited to platform 'windows5.0'
pub extern "user32" fn AdjustWindowRect(
lpRect: ?*RECT,
dwStyle: WINDOW_STYLE,
bMenu: BOOL,
) callconv(@import("std").os.windows.WINAPI) BOOL;
// TODO: this type is limited to platform 'windows5.0'
pub extern "user32" fn AdjustWindowRectEx(
pub extern "user32" fn AdjustWindowRectExForDpi(
lpRect: ?*RECT,
dwStyle: WINDOW_STYLE,
bMenu: BOOL,
dwExStyle: WINDOW_EX_STYLE,
dpi: u32,
) callconv(@import("std").os.windows.WINAPI) BOOL;
// TODO: this type is limited to platform 'windows5.0'
@ -271,6 +271,14 @@ pub extern "user32" fn SetWindowLongW(
dwNewLong: i32,
) callconv(@import("std").os.windows.WINAPI) i32;
pub extern "user32" fn BringWindowToTop(
hWnd: ?HWND,
) callconv(@import("std").os.windows.WINAPI) BOOL;
pub extern "user32" fn SetForegroundWindow(
hWnd: ?HWND,
) callconv(@import("std").os.windows.WINAPI) BOOL;
//pub extern "user32" fn SetWindowPos(hWnd: HWND, hWndInsertAfter: HWND, X: i32, Y: i32, cx: i32, cy: i32, uFlags: u32) callconv(WINAPI) BOOL;
pub extern "user32" fn SetWindowPos(
hWnd: ?HWND,
@ -1206,13 +1214,13 @@ pub const LPDIENUMDEVICEOBJECTSCALLBACKW = switch (@import("builtin").zig_backen
) callconv(@import("std").os.windows.WINAPI) BOOL,
};
pub const IDI_APPLICATION = 32512;
pub const IDI_APPLICATION: [*:0]align(1) const u16 = @ptrFromInt(32512);
pub extern "user32" fn LoadIconW(
hInstance: ?HINSTANCE,
lpIconName: ?[*:0]align(1) const u16,
) callconv(@import("std").os.windows.WINAPI) ?HICON;
pub const IDC_ARROW = 32512;
pub const IDC_ARROW: [*:0]align(1) const u16 = @ptrFromInt(32512);
pub const IDC_HAND = 32649;
pub const IDC_HELP = 32651;
pub const IDC_IBEAM = 32513;
@ -1636,6 +1644,7 @@ pub const WM_QUERYOPEN = @as(u32, 19);
pub const WM_ENDSESSION = @as(u32, 22);
pub const WM_QUIT = @as(u32, 18);
pub const WM_GETMINMAXINFO = @as(u32, 36);
pub const WM_WINDOWPOSCHANGED = @as(u32, 71);
pub const WM_KEYFIRST = @as(u32, 256);
pub const WM_KEYDOWN = @as(u32, 256);
pub const WM_KEYUP = @as(u32, 257);
@ -1712,6 +1721,7 @@ pub const WM_SIZING = @as(u32, 532);
pub const WM_CAPTURECHANGED = @as(u32, 533);
pub const WM_MOVING = @as(u32, 534);
pub const WM_POWERBROADCAST = @as(u32, 536);
pub const WM_DPICHANGED = @as(u32, 736);
pub const KEYBD_EVENT_FLAGS = packed struct(u32) {
EXTENDEDKEY: u1 = 0,
@ -2604,6 +2614,10 @@ pub extern "kernel32" fn GetProcAddress(
hModule: ?*anyopaque,
lpProcName: ?[*:0]const u8,
) callconv(WINAPI) ?*const fn () callconv(WINAPI) isize;
pub extern "kernel32" fn ExitProcess(
uExitCode: u32,
) callconv(@import("std").os.windows.WINAPI) noreturn;
pub const INFINITE = 4294967295;
pub const SECURITY_ATTRIBUTES = extern struct {
nLength: u32,
@ -4157,3 +4171,83 @@ pub const AUDCLNT_E_HEADTRACKING_ENABLED = -2004287440;
pub const AUDCLNT_E_HEADTRACKING_UNSUPPORTED = -2004287424;
pub const AUDCLNT_E_EFFECT_NOT_AVAILABLE = -2004287423;
pub const AUDCLNT_E_EFFECT_STATE_READ_ONLY = -2004287422;
pub extern "user32" fn GetDpiForWindow(
hwnd: ?HWND,
) callconv(@import("std").os.windows.WINAPI) u32;
pub fn dpiFromHwnd(hwnd: HWND) u32 {
const value = GetDpiForWindow(hwnd);
if (value == 0) std.debug.panic(
"GetDpiForWindow failed, error={}",
.{GetLastError()},
);
return value;
}
fn getDpiFactor(dpi: u32) f32 {
std.debug.assert(dpi >= 96);
return @as(f32, @floatFromInt(dpi)) / 96.0;
}
pub fn pxFromPt(comptime T: type, value: T, dpi: u32) T {
switch (@typeInfo(T)) {
.float => return value * getDpiFactor(dpi),
.int => return @intFromFloat(@round(@as(f32, @floatFromInt(value)) * getDpiFactor(dpi))),
else => @compileError("scaleDpi does not support type " ++ @typeName(@TypeOf(value))),
}
}
pub fn ptFromPx(comptime T: type, value: T, dpi: u32) T {
switch (@typeInfo(T)) {
.float => return value / getDpiFactor(dpi),
.int => return @intFromFloat(@round(@as(f32, @floatFromInt(value)) / getDpiFactor(dpi))),
else => @compileError("scaleDpi does not support type " ++ @typeName(@TypeOf(value))),
}
}
pub const CREATESTRUCTW = extern struct {
lpCreateParams: ?*anyopaque,
hInstance: ?HINSTANCE,
hMenu: ?HMENU,
hwndParent: ?HWND,
cy: i32,
cx: i32,
y: i32,
x: i32,
style: i32,
lpszName: ?[*:0]const u16,
lpszClass: ?[*:0]const u16,
dwExStyle: u32,
};
pub fn loword(value: anytype) u16 {
switch (@typeInfo(@TypeOf(value))) {
.int => |int| switch (int.signedness) {
.signed => return loword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))),
.unsigned => return if (int.bits <= 16) value else @intCast(0xffff & value),
},
else => {},
}
@compileError("unsupported type " ++ @typeName(@TypeOf(value)));
}
pub fn hiword(value: anytype) u16 {
switch (@typeInfo(@TypeOf(value))) {
.int => |int| switch (int.signedness) {
.signed => return hiword(@as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = int.bits } }), @bitCast(value))),
.unsigned => return @intCast(0xffff & (value >> 16)),
},
else => {},
}
@compileError("unsupported type " ++ @typeName(@TypeOf(value)));
}
fn xFromLparam(lparam: LPARAM) i16 {
return @bitCast(loword(lparam));
}
fn yFromLparam(lparam: LPARAM) i16 {
return @bitCast(hiword(lparam));
}
pub fn pointFromLparam(lparam: LPARAM) POINT {
return POINT{ .x = xFromLparam(lparam), .y = yFromLparam(lparam) };
}