400 lines
12 KiB
Zig
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();
|
|
}
|
|
};
|