const std = @import("std"); const assert = std.debug.assert; const c = @cImport({ @cInclude("MiniFB.h"); @cInclude("MiniFB_enums.h"); }); const testing = std.testing; pub const Rgb = packed struct { b: u8, g: u8, r: u8, _reserved: u8 = 0, }; pub const Key = enum(c_int) { unknown = c.KB_KEY_UNKNOWN, space = c.KB_KEY_SPACE, apostrophe = c.KB_KEY_APOSTROPHE, comma = c.KB_KEY_COMMA, minus = c.KB_KEY_MINUS, period = c.KB_KEY_PERIOD, slash = c.KB_KEY_SLASH, @"0" = c.KB_KEY_0, @"1" = c.KB_KEY_1, @"2" = c.KB_KEY_2, @"3" = c.KB_KEY_3, @"4" = c.KB_KEY_4, @"5" = c.KB_KEY_5, @"6" = c.KB_KEY_6, @"7" = c.KB_KEY_7, @"8" = c.KB_KEY_8, @"9" = c.KB_KEY_9, semicolon = c.KB_KEY_SEMICOLON, equal = c.KB_KEY_EQUAL, a = c.KB_KEY_A, b = c.KB_KEY_B, c = c.KB_KEY_C, d = c.KB_KEY_D, e = c.KB_KEY_E, f = c.KB_KEY_F, g = c.KB_KEY_G, h = c.KB_KEY_H, i = c.KB_KEY_I, j = c.KB_KEY_J, k = c.KB_KEY_K, l = c.KB_KEY_L, m = c.KB_KEY_M, n = c.KB_KEY_N, o = c.KB_KEY_O, p = c.KB_KEY_P, q = c.KB_KEY_Q, r = c.KB_KEY_R, s = c.KB_KEY_S, t = c.KB_KEY_T, u = c.KB_KEY_U, v = c.KB_KEY_V, w = c.KB_KEY_W, x = c.KB_KEY_X, y = c.KB_KEY_Y, z = c.KB_KEY_Z, left_bracket = c.KB_KEY_LEFT_BRACKET, backslash = c.KB_KEY_BACKSLASH, right_bracket = c.KB_KEY_RIGHT_BRACKET, grave_accent = c.KB_KEY_GRAVE_ACCENT, world_1 = c.KB_KEY_WORLD_1, world_2 = c.KB_KEY_WORLD_2, escape = c.KB_KEY_ESCAPE, enter = c.KB_KEY_ENTER, tab = c.KB_KEY_TAB, backspace = c.KB_KEY_BACKSPACE, insert = c.KB_KEY_INSERT, delete = c.KB_KEY_DELETE, right = c.KB_KEY_RIGHT, left = c.KB_KEY_LEFT, down = c.KB_KEY_DOWN, up = c.KB_KEY_UP, page_up = c.KB_KEY_PAGE_UP, page_down = c.KB_KEY_PAGE_DOWN, home = c.KB_KEY_HOME, end = c.KB_KEY_END, caps_lock = c.KB_KEY_CAPS_LOCK, scroll_lock = c.KB_KEY_SCROLL_LOCK, num_lock = c.KB_KEY_NUM_LOCK, print_screen = c.KB_KEY_PRINT_SCREEN, pause = c.KB_KEY_PAUSE, f1 = c.KB_KEY_F1, f2 = c.KB_KEY_F2, f3 = c.KB_KEY_F3, f4 = c.KB_KEY_F4, f5 = c.KB_KEY_F5, f6 = c.KB_KEY_F6, f7 = c.KB_KEY_F7, f8 = c.KB_KEY_F8, f9 = c.KB_KEY_F9, f10 = c.KB_KEY_F10, f11 = c.KB_KEY_F11, f12 = c.KB_KEY_F12, f13 = c.KB_KEY_F13, f14 = c.KB_KEY_F14, f15 = c.KB_KEY_F15, f16 = c.KB_KEY_F16, f17 = c.KB_KEY_F17, f18 = c.KB_KEY_F18, f19 = c.KB_KEY_F19, f20 = c.KB_KEY_F20, f21 = c.KB_KEY_F21, f22 = c.KB_KEY_F22, f23 = c.KB_KEY_F23, f24 = c.KB_KEY_F24, f25 = c.KB_KEY_F25, kp_0 = c.KB_KEY_KP_0, kp_1 = c.KB_KEY_KP_1, kp_2 = c.KB_KEY_KP_2, kp_3 = c.KB_KEY_KP_3, kp_4 = c.KB_KEY_KP_4, kp_5 = c.KB_KEY_KP_5, kp_6 = c.KB_KEY_KP_6, kp_7 = c.KB_KEY_KP_7, kp_8 = c.KB_KEY_KP_8, kp_9 = c.KB_KEY_KP_9, kp_decimal = c.KB_KEY_KP_DECIMAL, kp_divide = c.KB_KEY_KP_DIVIDE, kp_multiply = c.KB_KEY_KP_MULTIPLY, kp_subtract = c.KB_KEY_KP_SUBTRACT, kp_add = c.KB_KEY_KP_ADD, kp_enter = c.KB_KEY_KP_ENTER, kp_equal = c.KB_KEY_KP_EQUAL, left_shift = c.KB_KEY_LEFT_SHIFT, left_control = c.KB_KEY_LEFT_CONTROL, left_alt = c.KB_KEY_LEFT_ALT, left_super = c.KB_KEY_LEFT_SUPER, right_shift = c.KB_KEY_RIGHT_SHIFT, right_control = c.KB_KEY_RIGHT_CONTROL, right_alt = c.KB_KEY_RIGHT_ALT, right_super = c.KB_KEY_RIGHT_SUPER, menu = c.KB_KEY_MENU, _, }; pub const KeyMod = packed struct { shift: bool = false, control: bool = false, alt: bool = false, super: bool = false, capsLock: bool = false, numLock: bool = false, _reserved: u26 = 0, fn putName(present: bool, name: []const u8, names: *[6][]const u8, index: *usize) void { if (present) { names.*[index.*] = name; index.* += 1; } } pub fn format(self: KeyMod, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { var names: [6][]const u8 = undefined; var i: usize = 0; if (fmt.len > 0) { @compileError("Unknown format character: '" ++ fmt ++ "'"); } putName(self.shift, "Shift", &names, &i); putName(self.control, "Control", &names, &i); putName(self.alt, "Alt", &names, &i); putName(self.super, "Super", &names, &i); putName(self.capsLock, "Caps_Lock", &names, &i); putName(self.numLock, "Num_Lock", &names, &i); var first = true; for (names[0..i]) |name| { if (!first) { _ = try writer.write("+"); } _ = try writer.write(name); first = false; } } }; pub const MouseButton = enum(c_int) { @"0" = c.MOUSE_BTN_0, @"1" = c.MOUSE_BTN_1, @"2" = c.MOUSE_BTN_2, @"3" = c.MOUSE_BTN_3, @"4" = c.MOUSE_BTN_4, @"5" = c.MOUSE_BTN_5, @"6" = c.MOUSE_BTN_6, @"7" = c.MOUSE_BTN_7, _, }; pub fn Window(comptime TUserData: type) type { return extern struct { cwin: *c.mfb_window, pub const UpdateError = error{ InvalidWindow, InvalidBuffer, InternalError, }; pub const UpdateState = enum { ok, exit, pub fn from(state: c.mfb_update_state) UpdateError!UpdateState { return switch (state) { c.STATE_OK => UpdateState.ok, c.STATE_EXIT => UpdateState.exit, c.STATE_INVALID_WINDOW => UpdateError.InvalidWindow, c.STATE_INVALID_BUFFER => UpdateError.InvalidBuffer, else => UpdateError.InternalError, }; } }; pub const OpenFlags = packed struct { resizable: bool = false, fullscreen: bool = false, fullscreenDesktop: bool = false, borderless: bool = false, alwaysOnTop: bool = false, reserved: u27 = 0, }; pub fn open(title: [*:0]const u8, width: u32, height: u32, flags: OpenFlags) !Window(TUserData) { const intFlags = @bitCast(u32, flags); const cTitle = @as([*c]const u8, title); const cwin: ?*c.mfb_window = c.mfb_open_ex(cTitle, width, height, intFlags); if (cwin) |value| { const win = Window(TUserData){ .cwin = value }; assert(@bitCast(usize, win) == @ptrToInt(win.cwin)); return win; } else { return error.ItBroke; } } pub fn close(self: Window(TUserData)) void { c.mfb_close(self.cwin); } pub fn waitSync(self: Window(TUserData)) bool { return c.mfb_wait_sync(self.cwin); } pub fn update(self: Window(TUserData), buffer: []Rgb) UpdateError!UpdateState { return UpdateState.from(c.mfb_update(self.cwin, @ptrCast(*anyopaque, buffer.ptr))); } pub fn updateEvents(self: Window(TUserData)) UpdateError!UpdateState { return UpdateState.from(c.mfb_update_events(self.cwin)); } pub fn updateEx(self: Window(TUserData), buffer: []Rgb, width: u32, height: u32) UpdateError!UpdateState { return UpdateState.from(c.mfb_update_ex(self.cwin), @ptrCast(*anyopaque, buffer), width, height); } pub fn setUserData(self: Window(TUserData), data: *TUserData) void { c.mfb_set_user_data(self.cwin, data); } pub fn getUserData(self: Window(TUserData)) ?*TUserData { var cData = c.mfb_get_user_data(self.cwin); return @ptrCast(?*TUserData, @alignCast(@alignOf(TUserData), cData)); } pub const ActiveFunc = fn (win: Window(TUserData), isActive: bool) callconv(.C) void; pub fn setActiveCallback(self: Window(TUserData), callback: ActiveFunc) void { c.mfb_set_active_callback(self.cwin, @ptrCast(c.mfb_active_func, callback)); } pub const ResizeFunc = fn (win: Window(TUserData), width: i32, height: i32) callconv(.C) void; pub fn setResizeCallback(self: Window(TUserData), callback: ResizeFunc) void { c.mfb_set_resize_callback(self.cwin, @ptrCast(c.mfb_resize_func, callback)); } pub const MouseButtonFunc = fn (win: Window(TUserData), mouse_button: MouseButton, key_mod: KeyMod, is_pressed: bool) callconv(.C) void; pub fn setMouseButtonCallback(self: Window(TUserData), callback: MouseButtonFunc) void { c.mfb_set_mouse_button_callback(self.cwin, @ptrCast(c.mfb_mouse_button_func, callback)); } pub const MouseMoveFunc = fn (win: Window(TUserData), x: i32, y: i32) callconv(.C) void; pub fn setMouseMoveCallback(self: Window(TUserData), callback: MouseMoveFunc) void { c.mfb_set_mouse_move_callback(self.cwin, @ptrCast(c.mfb_mouse_move_func, callback)); } pub const MouseScrollFunc = fn (win: Window(TUserData), key_mod: KeyMod, delta_x: f32, delty_y: f32) callconv(.C) void; pub fn setMouseScrollCallback(self: Window(TUserData), callback: MouseScrollFunc) void { c.mfb_set_mouse_scroll_callback(self.cwin, @ptrCast(c.mfb_mouse_scroll_func, callback)); } pub const KeyboardFunc = fn (win: Window(TUserData), key: Key, key_mod: KeyMod, is_pressed: bool) callconv(.C) void; pub fn setKeyboardCallback(self: Window(TUserData), callback: KeyboardFunc) void { c.mfb_set_keyboard_callback(self.cwin, @ptrCast(c.mfb_keyboard_func, callback)); } pub fn setViewport(self: Window(TUserData), offset_x: u32, offset_y: u32, width: u32, height: u32) bool { return c.mfb_set_viewport(self.cwin, offset_x, offset_y, width, height); } pub const MonitorScale = struct { x: f32, y: f32, }; pub fn getMonitorScale(self: Window(TUserData)) MonitorScale { var scale = MonitorScale{ .x = undefined, .y = undefined, }; c.mfb_get_monitor_scale(self.cwin, &scale.x, &scale.y); return scale; } }; } test "user data is null if never set" { const win = try Window(u64).open("abc", 100, 100, .{}); defer win.close(); const data = win.getUserData(); try testing.expectEqual(@as(?*u64, null), data); } test "user data is not null if previously set" { const win = try Window(u64).open("abc", 100, 100, .{}); defer win.close(); var data: u64 = 42; win.setUserData(&data); const expected: u64 = 42; try testing.expectEqual(expected, win.getUserData().?.*); } test "also works with smaller user data" { const win = try Window(u8).open("abc", 100, 100, .{}); defer win.close(); var data: u8 = 42; win.setUserData(&data); const expected: u8 = 42; try testing.expectEqual(expected, win.getUserData().?.*); } pub fn getTargetFPS() u32 { return c.mfb_get_target_fps(); } pub fn setTargetFPS(fps: u32) void { c.mfb_set_target_fps(fps); } test "set and get target FPS" { const max = 40; var fps: u32 = 30; while (fps < max) { setTargetFPS(fps); try std.testing.expectEqual(fps, getTargetFPS()); fps += 1; } } pub const Timer = extern struct { ctimer: *c.mfb_timer, pub fn init() !Timer { const ctimer: ?*c.mfb_timer = c.mfb_timer_create(); if (ctimer) |value| { return Timer{ .ctimer = value }; } else { return error.ItBroke; } } pub fn deinit(self: Timer) void { c.mfb_timer_destroy(self.ctimer); } pub fn reset(self: Timer) void { c.mfb_timer_reset(self.ctimer); } pub fn now(self: Timer) f64 { return c.mfb_timer_now(self.ctimer); } pub fn delta(self: Timer) f64 { return c.mfb_timer_delta(self.ctimer); } pub fn getFrequency() f64 { return c.mfb_timer_get_frequency(); } pub fn getResolution() f64 { return c.mfb_timer_get_resolution(); } };