293 lines
9.1 KiB
Zig
293 lines
9.1 KiB
Zig
const mfb = @import("minifb");
|
|
const std = @import("std");
|
|
const min = std.math.min;
|
|
const max = std.math.max;
|
|
|
|
const buffer_width = 800;
|
|
const buffer_height = 600;
|
|
const aspect_ratio = @intToFloat(comptime_float, buffer_width) / @intToFloat(comptime_float, buffer_height);
|
|
const cursor_width = 25;
|
|
|
|
fn grey(value: u8) mfb.Rgb {
|
|
return mfb.Rgb{ .r = value, .g = value, .b = value };
|
|
}
|
|
|
|
const Coords = struct {
|
|
x: i32,
|
|
y: i32,
|
|
};
|
|
|
|
const FCoords = struct {
|
|
x: f32,
|
|
y: f32,
|
|
|
|
pub fn toCoords(self: FCoords) Coords {
|
|
return Coords{
|
|
.x = @floatToInt(i32, self.x),
|
|
.y = @floatToInt(i32, self.y),
|
|
};
|
|
}
|
|
};
|
|
|
|
const State = struct {
|
|
period: f64,
|
|
timer: mfb.Timer,
|
|
active: bool = true,
|
|
alloc: std.mem.Allocator,
|
|
cursor_pos: Coords = undefined,
|
|
rectangle_pos: FCoords = FCoords{
|
|
.x = @intToFloat(f32, buffer_width) / 2.0,
|
|
.y = @intToFloat(f32, buffer_height) / 2.0,
|
|
},
|
|
rectangle_width: f32 = @intToFloat(f32, buffer_width) / 3.0,
|
|
rectangle_height: f32 = @intToFloat(f32, buffer_height) / 3.0,
|
|
paused: bool = false,
|
|
cheat_code: []const u8 = "rave",
|
|
cheat_code_progress: usize = 0,
|
|
|
|
pub fn init(alloc: std.mem.Allocator, period: f64) std.mem.Allocator.Error!State {
|
|
return State{
|
|
.period = period,
|
|
.timer = try mfb.Timer.init(),
|
|
.alloc = alloc,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: State) void {
|
|
self.timer.deinit();
|
|
}
|
|
|
|
pub fn cheatInput(self: *State, char: u32) void {
|
|
if (self.isCheating()) {
|
|
return;
|
|
}
|
|
if (char == self.cheat_code[self.cheat_code_progress]) {
|
|
self.cheat_code_progress += 1;
|
|
} else {
|
|
self.cheat_code_progress = 0;
|
|
}
|
|
}
|
|
|
|
fn isCheating(self: State) bool {
|
|
return self.cheat_code_progress == self.cheat_code.len;
|
|
}
|
|
|
|
pub fn render(self: State, buf: Buffer) void {
|
|
const period = if (!self.isCheating()) self.period else self.period / 10;
|
|
var bgValue = @rem(self.timer.now(), period) / period * 512;
|
|
if (bgValue >= 256) {
|
|
bgValue = 512 - bgValue;
|
|
}
|
|
|
|
const rectColor = if (self.active) mfb.Rgb{ .r = 0, .g = 255, .b = 0 } else mfb.Rgb{ .r = 255, .g = 0, .b = 0 };
|
|
|
|
buf.drawRectangle(.{ .x = 0, .y = 0 }, .{ .x = buffer_width, .y = buffer_height }, grey(@floatToInt(u8, bgValue)));
|
|
buf.drawRectangle(
|
|
(FCoords{
|
|
.x = self.rectangle_pos.x - self.rectangle_width / 2,
|
|
.y = self.rectangle_pos.y - self.rectangle_height / 2,
|
|
}).toCoords(),
|
|
(FCoords{
|
|
.x = self.rectangle_pos.x + self.rectangle_width / 2,
|
|
.y = self.rectangle_pos.y + self.rectangle_height / 2,
|
|
}).toCoords(),
|
|
rectColor,
|
|
);
|
|
|
|
// Draw cursor
|
|
buf.drawRectangle(
|
|
.{ .x = self.cursor_pos.x - cursor_width / 2, .y = self.cursor_pos.y - cursor_width / 2 },
|
|
.{ .x = self.cursor_pos.x + cursor_width / 2, .y = self.cursor_pos.y + cursor_width / 2 },
|
|
mfb.Rgb{ .r = 61, .g = 97, .b = 170 },
|
|
);
|
|
}
|
|
};
|
|
|
|
const Buffer = struct {
|
|
alloc: std.mem.Allocator,
|
|
slice: []mfb.Rgb,
|
|
width: u32,
|
|
height: u32,
|
|
|
|
pub fn init(alloc: std.mem.Allocator, width: u32, height: u32) std.mem.Allocator.Error!Buffer {
|
|
const slice = try alloc.alloc(mfb.Rgb, width * height);
|
|
return Buffer{
|
|
.alloc = alloc,
|
|
.slice = slice,
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: Buffer) void {
|
|
self.alloc.free(self.slice);
|
|
}
|
|
|
|
pub fn setPixel(self: Buffer, coords: Coords, color: mfb.Rgb) void {
|
|
const x = @intCast(u32, coords.x);
|
|
const y = @intCast(u32, coords.y);
|
|
self.slice[y * self.width + x] = color;
|
|
}
|
|
|
|
pub fn drawRectangle(self: Buffer, a: Coords, b: Coords, color: mfb.Rgb) void {
|
|
var start = Coords{
|
|
.x = max(0, min(a.x, b.x)),
|
|
.y = max(0, min(a.y, b.y)),
|
|
};
|
|
var end = Coords{
|
|
.x = min(@intCast(i32, self.width), max(a.x, b.x)),
|
|
.y = min(@intCast(i32, self.height), max(a.y, b.y)),
|
|
};
|
|
|
|
var y = start.y;
|
|
while (y < end.y) {
|
|
var x = start.x;
|
|
while (x < end.x) {
|
|
self.setPixel(.{ .x = x, .y = y }, color);
|
|
x += 1;
|
|
}
|
|
y += 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
fn handleActive(win: mfb.Window(State), is_active: bool) callconv(.C) void {
|
|
win.getUserData().?.*.active = is_active;
|
|
}
|
|
|
|
fn handleResize(win: mfb.Window(State), width: i32, height: i32) callconv(.C) void {
|
|
const win_aspect_ratio = @intToFloat(f32, width) / @intToFloat(f32, height);
|
|
var vp_height: i32 = height;
|
|
var vp_width: i32 = width;
|
|
var vp_offset_x: i32 = 0;
|
|
var vp_offset_y: i32 = 0;
|
|
if (win_aspect_ratio > aspect_ratio) {
|
|
vp_width = @floatToInt(i32, @intToFloat(f32, height) * aspect_ratio);
|
|
vp_offset_x = @divTrunc(width - vp_width, 2);
|
|
} else {
|
|
vp_height = @floatToInt(i32, @intToFloat(f32, width) / aspect_ratio);
|
|
vp_offset_y = @divTrunc(height - vp_height, 2);
|
|
}
|
|
const did_set = win.setViewport(
|
|
@intCast(u32, vp_offset_x),
|
|
@intCast(u32, vp_offset_y),
|
|
@intCast(u32, vp_width),
|
|
@intCast(u32, vp_height),
|
|
);
|
|
|
|
if (!did_set) {
|
|
std.log.warn("did not set viewport!", .{});
|
|
}
|
|
}
|
|
|
|
fn handleMouseButton(win: mfb.Window(State), mouse_button: mfb.MouseButton, key_mod: mfb.KeyMod, is_pressed: bool) callconv(.C) void {
|
|
const up_down: []const u8 = if (is_pressed) "down" else "up";
|
|
std.log.info("{any} mouse{d} {s}", .{ key_mod, @enumToInt(mouse_button), up_down });
|
|
|
|
var buf_string = [_]u8{'.', ' ', '.', ' ', '.', ' ', '.', ' ', '.', ' ', '.', ' ', '.', ' ', '.'};
|
|
for (win.getMouseButtonBuffer().*) |button, i| {
|
|
if (button) {
|
|
buf_string[i * 2] = '*';
|
|
}
|
|
}
|
|
std.log.info("Mouse button buffer: {s}", .{&buf_string});
|
|
}
|
|
|
|
fn handleMouseMove(win: mfb.Window(State), x: i32, y: i32) callconv(.C) void {
|
|
win.getUserData().?.*.cursor_pos = .{ .x = x, .y = y };
|
|
}
|
|
|
|
fn handleMouseScroll(win: mfb.Window(State), key_mod: mfb.KeyMod, delta_x: f32, delta_y: f32) callconv(.C) void {
|
|
_ = delta_x;
|
|
|
|
const scroll_speed = 3.5;
|
|
|
|
var state = win.getUserData().?;
|
|
std.log.info("scroll dx={d} dy={d}", .{ delta_x, delta_y });
|
|
if (key_mod.control) {
|
|
if (key_mod.shift) {
|
|
state.rectangle_width += delta_y * scroll_speed;
|
|
} else {
|
|
state.rectangle_height += delta_y * scroll_speed;
|
|
}
|
|
} else {
|
|
if (key_mod.shift) {
|
|
state.rectangle_pos.x += delta_y * scroll_speed;
|
|
} else {
|
|
state.rectangle_pos.y -= delta_y * scroll_speed;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handleKeyboard(win: mfb.Window(State), key: mfb.Key, _: mfb.KeyMod, is_pressed: bool) callconv(.C) void {
|
|
if (key == mfb.Key.escape and is_pressed) {
|
|
win.close();
|
|
} else if (key == mfb.Key.space and is_pressed) {
|
|
var state = win.getUserData().?;
|
|
state.*.paused = !state.*.paused;
|
|
}
|
|
|
|
const out = std.io.getStdOut().writer();
|
|
var has_written = false;
|
|
for (win.getKeyBuffer()) |buf_key, i| {
|
|
if (buf_key) {
|
|
if (!has_written) {
|
|
_ = out.write("Keyboard buffer:") catch unreachable;
|
|
}
|
|
std.fmt.format(out, " {}", .{@intToEnum(mfb.Key, i)}) catch unreachable;
|
|
has_written = true;
|
|
}
|
|
}
|
|
if (has_written) {
|
|
out.writeByte('\n') catch unreachable;
|
|
}
|
|
}
|
|
|
|
fn handleCharInput(win: mfb.Window(State), char: u32) callconv(.C) void {
|
|
win.getUserData().?.cheatInput(char);
|
|
}
|
|
|
|
pub fn main() !void {
|
|
var gp_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const alloc = gp_allocator.allocator();
|
|
|
|
var state = try State.init(alloc, 3);
|
|
defer state.deinit();
|
|
|
|
var win = mfb.Window(State).open("Hello minifb-zig", buffer_width, buffer_height, .{ .resizable = true, .always_on_top = true }) catch unreachable;
|
|
mfb.setTargetFPS(30);
|
|
win.setUserData(&state);
|
|
|
|
const scale = win.getMonitorScale();
|
|
try std.io.getStdOut().writer().print(
|
|
\\Monitor scale: {d} * {d}
|
|
\\
|
|
\\Press spacebar to pause, escape to quit.
|
|
\\Try [Ctrl+][Shift+]scroll wheel to change the colored rectangle.
|
|
\\Type "{s}" to get crazy.
|
|
\\
|
|
, .{ scale.x, scale.y, state.cheat_code });
|
|
|
|
win.setActiveCallback(handleActive);
|
|
win.setResizeCallback(handleResize);
|
|
win.setMouseButtonCallback(handleMouseButton);
|
|
win.setMouseMoveCallback(handleMouseMove);
|
|
win.setMouseScrollCallback(handleMouseScroll);
|
|
win.setKeyboardCallback(handleKeyboard);
|
|
win.setCharInputCallback(handleCharInput);
|
|
|
|
var buf = try Buffer.init(alloc, buffer_width, buffer_height);
|
|
defer buf.deinit();
|
|
|
|
state.render(buf);
|
|
while (win.waitSync()) {
|
|
state.render(buf);
|
|
// TODO Handle mfb state
|
|
if (state.paused) {
|
|
_ = win.updateEvents() catch unreachable;
|
|
} else {
|
|
_ = win.update(buf.slice) catch unreachable;
|
|
}
|
|
}
|
|
}
|