400 lines
12 KiB
Zig

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();
}
};