init
This commit is contained in:
commit
5a0e3e311a
19 changed files with 5959 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
zig-cache/
|
||||
zig-out/
|
||||
deps.zig
|
||||
gyro.lock
|
||||
.gyro
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "zalgebra"]
|
||||
path = zalgebra
|
||||
url = https://github.com/kooparse/zalgebra.git
|
17
README.md
Normal file
17
README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# bingus
|
||||
|
||||
An OpenGL based bingo GUI, mostly meant for [porting bingo](https://git.tilera.org/tilera/portingbingo)
|
||||
|
||||
## dependencies
|
||||
- OpenGL
|
||||
- GLFW3
|
||||
- freetype
|
||||
- Zig
|
||||
|
||||
```bash
|
||||
paru -S --needed \
|
||||
glfw-x11 \ # or glfw-wayland
|
||||
glew \
|
||||
freetype2 \
|
||||
zig-git # `zig` arch package is completely outdated
|
||||
```
|
44
build.zig
Normal file
44
build.zig
Normal file
|
@ -0,0 +1,44 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.build.Builder) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const exe = b.addExecutable("bingus", "src/main.zig");
|
||||
exe.setTarget(target);
|
||||
exe.setBuildMode(mode);
|
||||
|
||||
exe.addPackage(std.build.Pkg{
|
||||
.name = "zalgebra",
|
||||
.source = .{ .path = "zalgebra/src/main.zig" },
|
||||
});
|
||||
|
||||
exe.linkLibC();
|
||||
exe.linkSystemLibrary("glfw3");
|
||||
exe.linkSystemLibrary("freetype2");
|
||||
|
||||
exe.install();
|
||||
|
||||
const run_cmd = exe.run();
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_tests = b.addTest("src/main.zig");
|
||||
exe_tests.setTarget(target);
|
||||
exe_tests.setBuildMode(mode);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&exe_tests.step);
|
||||
}
|
47
src/Color.zig
Normal file
47
src/Color.zig
Normal file
|
@ -0,0 +1,47 @@
|
|||
// https://draculatheme.com
|
||||
pub const background = fromBytes(.{ 0x28, 0x2a, 0x36, 0xb0 });
|
||||
pub const square_background = fromBytes(.{ 0x44, 0x47, 0x5a, 0xff });
|
||||
pub const square_frame = fromBytes(.{ 0x50, 0xfa, 0x7b, 0xff });
|
||||
|
||||
r: f32 = 0.0,
|
||||
g: f32 = 0.0,
|
||||
b: f32 = 0.0,
|
||||
a: f32 = 1.0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn fromBytes(data: [4]u8) Self {
|
||||
return fromArray(
|
||||
@as(@Vector(4, f32), [_]f32{
|
||||
@intToFloat(f32, data[0]),
|
||||
@intToFloat(f32, data[1]),
|
||||
@intToFloat(f32, data[2]),
|
||||
@intToFloat(f32, data[3]),
|
||||
}) / @splat(4, @as(f32, 255.0)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn fromArray(data: [4]f32) Self {
|
||||
return .{
|
||||
.r = data[0],
|
||||
.g = data[1],
|
||||
.b = data[2],
|
||||
.a = data[3],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toArray(self: Self) [4]f32 {
|
||||
return .{
|
||||
self.r,
|
||||
self.g,
|
||||
self.b,
|
||||
self.a,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mul(self: Self, other: Self) Self {
|
||||
return fromArray(
|
||||
@as(@Vector(4, f32), self.toArray()) *
|
||||
@as(@Vector(4, f32), other.toArray()),
|
||||
);
|
||||
}
|
30
src/GlState.zig
Normal file
30
src/GlState.zig
Normal file
|
@ -0,0 +1,30 @@
|
|||
const std = @import("std");
|
||||
const zalgebra = @import("zalgebra");
|
||||
|
||||
pub const inital_win_size = zalgebra.Vec2_i32.new(800, 800);
|
||||
|
||||
// I'm not a fan of global state either, but this is really not possible to do
|
||||
// otherwise becuase OpenGL also has global state.
|
||||
pub var state: Self = undefined;
|
||||
|
||||
current_screen_size: zalgebra.Vec2_i32 = inital_win_size,
|
||||
|
||||
vbo: c_uint = 0,
|
||||
vao: c_uint = 0,
|
||||
|
||||
shader_program: c_uint = 0,
|
||||
|
||||
font_vbo: c_uint = 0,
|
||||
font_vao: c_uint = 0,
|
||||
|
||||
font_shader_program: c_uint = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init() !void {
|
||||
state = .{};
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
state = undefined;
|
||||
}
|
44
src/bingoloader.zig
Normal file
44
src/bingoloader.zig
Normal file
|
@ -0,0 +1,44 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const BingoData = struct {
|
||||
bingos: [][]const u8,
|
||||
arena: std.heap.ArenaAllocator,
|
||||
|
||||
pub fn deinit(self: *BingoData) void {
|
||||
self.arena.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn loadFile(path: []const u8, count: usize) !BingoData {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
|
||||
|
||||
const file = try std.fs.cwd().openFile(path, .{});
|
||||
defer file.close();
|
||||
const data = try file.readToEndAlloc(arena.allocator(), std.math.maxInt(usize));
|
||||
|
||||
var bingos = std.ArrayList([]const u8).init(std.heap.c_allocator);
|
||||
defer bingos.deinit();
|
||||
var data_iter = std.mem.tokenize(u8, data, ",\n");
|
||||
while (data_iter.next()) |bingo| {
|
||||
const trimmed = std.mem.trim(u8, bingo, "\t\n ");
|
||||
if (trimmed.len == 0)
|
||||
continue;
|
||||
try bingos.append(trimmed);
|
||||
}
|
||||
|
||||
var rng = std.rand.DefaultPrng.init(@bitCast(usize, std.time.microTimestamp()));
|
||||
rng.random().shuffle([]const u8, bingos.items);
|
||||
|
||||
const bingo_items = try arena.allocator().alloc([]const u8, count);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
bingo_items[i] = if (bingos.items.len > i) bingos.items[i] else "";
|
||||
}
|
||||
|
||||
return .{
|
||||
.bingos = bingo_items,
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
5
src/ffi.zig
Normal file
5
src/ffi.zig
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub const c = @cImport({
|
||||
@cInclude("freetype/freetype.h");
|
||||
@cInclude("GL/glew.h");
|
||||
@cInclude("GLFW/glfw3.h");
|
||||
});
|
212
src/font.zig
Normal file
212
src/font.zig
Normal file
|
@ -0,0 +1,212 @@
|
|||
const std = @import("std");
|
||||
const zalgebra = @import("zalgebra");
|
||||
const c = @import("ffi.zig").c;
|
||||
const gl = @import("gl.zig");
|
||||
const glutil = @import("glutil.zig");
|
||||
const GlState = @import("GlState.zig");
|
||||
|
||||
pub const Character = struct {
|
||||
tex_id: c_uint,
|
||||
size: zalgebra.Vec2_i32,
|
||||
/// Offset from the left/top
|
||||
bearing: zalgebra.Vec2_i32,
|
||||
advance: c_long,
|
||||
};
|
||||
|
||||
pub const CharMap = std.AutoHashMap(u8, Character);
|
||||
|
||||
const font_data = @embedFile("iosevka-regular.ttf");
|
||||
|
||||
pub var current_projection_matrix: zalgebra.Mat4 = undefined;
|
||||
|
||||
pub fn load(chars: *CharMap, line_height: *c_long) !void {
|
||||
std.log.info("loading font", .{});
|
||||
var freetype: c.FT_Library = undefined;
|
||||
if (c.FT_Init_FreeType(&freetype) != 0) {
|
||||
return error.FreetypeInit;
|
||||
}
|
||||
defer _ = c.FT_Done_FreeType(freetype);
|
||||
|
||||
var font_face: c.FT_Face = undefined;
|
||||
if (c.FT_New_Memory_Face(
|
||||
freetype,
|
||||
font_data,
|
||||
font_data.len,
|
||||
0,
|
||||
&font_face,
|
||||
) != 0) {
|
||||
return error.FontLoading;
|
||||
}
|
||||
defer _ = c.FT_Done_Face(font_face);
|
||||
|
||||
if (c.FT_Set_Pixel_Sizes(font_face, 0, 48) != 0) {
|
||||
return error.SetPixelSizes;
|
||||
}
|
||||
|
||||
line_height.* = (font_face.*.size.*.metrics.ascender -
|
||||
font_face.*.size.*.metrics.descender) >> 5;
|
||||
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
|
||||
var char_idx: u8 = 0;
|
||||
// TODO: also load some fancy chars
|
||||
while (char_idx < 255) : (char_idx += 1) {
|
||||
if (c.FT_Load_Char(font_face, char_idx, c.FT_LOAD_RENDER) != 0) {
|
||||
std.log.warn("freetype failed to load glyph {c}", .{char_idx});
|
||||
continue;
|
||||
}
|
||||
|
||||
var texture: c_uint = 0;
|
||||
gl.genTextures(1, &texture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RED,
|
||||
@intCast(c_int, font_face.*.glyph.*.bitmap.width),
|
||||
@intCast(c_int, font_face.*.glyph.*.bitmap.rows),
|
||||
0,
|
||||
gl.RED,
|
||||
gl.UNSIGNED_BYTE,
|
||||
font_face.*.glyph.*.bitmap.buffer,
|
||||
);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
|
||||
try chars.put(char_idx, .{
|
||||
.tex_id = texture,
|
||||
.size = zalgebra.Vec2_i32.new(
|
||||
@intCast(i32, font_face.*.glyph.*.bitmap.width),
|
||||
@intCast(i32, font_face.*.glyph.*.bitmap.rows),
|
||||
),
|
||||
.bearing = zalgebra.Vec2_i32.new(
|
||||
font_face.*.glyph.*.bitmap_left,
|
||||
font_face.*.glyph.*.bitmap_top,
|
||||
),
|
||||
.advance = font_face.*.glyph.*.advance.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initFontGl() !void {
|
||||
std.log.info("creating font rendering pipeline", .{});
|
||||
|
||||
const vert = try glutil.loadShader(@embedFile("font_vert.glsl"), gl.VERTEX_SHADER);
|
||||
defer gl.deleteShader(vert);
|
||||
|
||||
const frag = try glutil.loadShader(@embedFile("font_frag.glsl"), gl.FRAGMENT_SHADER);
|
||||
defer gl.deleteShader(frag);
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vert);
|
||||
gl.attachShader(program, frag);
|
||||
gl.linkProgram(program);
|
||||
try glutil.checkProgramLinkError(program);
|
||||
|
||||
GlState.state.font_shader_program = program;
|
||||
|
||||
updateProgramProjectionMatrix(GlState.inital_win_size);
|
||||
|
||||
gl.genVertexArrays(1, &GlState.state.font_vao);
|
||||
gl.genBuffers(1, &GlState.state.font_vbo);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, GlState.state.font_vbo);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * 6 * 4, null, gl.DYNAMIC_DRAW);
|
||||
|
||||
gl.bindVertexArray(GlState.state.font_vao);
|
||||
gl.vertexAttribPointer(0, 4, gl.FLOAT, gl.FALSE, 4 * @sizeOf(f32), null);
|
||||
gl.enableVertexAttribArray(0);
|
||||
}
|
||||
|
||||
pub fn updateProgramProjectionMatrix(size: zalgebra.Vec2_i32) void {
|
||||
gl.useProgram(GlState.state.font_shader_program);
|
||||
current_projection_matrix = zalgebra.Mat4.orthographic(
|
||||
0.0,
|
||||
@intToFloat(f32, size.x()),
|
||||
0.0,
|
||||
@intToFloat(f32, size.y()),
|
||||
-1.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
gl.uniformMatrix4fv(
|
||||
gl.getUniformLocation(GlState.state.font_shader_program, "projection"),
|
||||
1,
|
||||
gl.FALSE,
|
||||
// elegant much
|
||||
&[_]f32{
|
||||
current_projection_matrix.data[0][0],
|
||||
current_projection_matrix.data[0][1],
|
||||
current_projection_matrix.data[0][2],
|
||||
current_projection_matrix.data[0][3],
|
||||
current_projection_matrix.data[1][0],
|
||||
current_projection_matrix.data[1][1],
|
||||
current_projection_matrix.data[1][2],
|
||||
current_projection_matrix.data[1][3],
|
||||
current_projection_matrix.data[2][0],
|
||||
current_projection_matrix.data[2][1],
|
||||
current_projection_matrix.data[2][2],
|
||||
current_projection_matrix.data[2][3],
|
||||
current_projection_matrix.data[3][0],
|
||||
current_projection_matrix.data[3][1],
|
||||
current_projection_matrix.data[3][2],
|
||||
current_projection_matrix.data[3][3],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn drawString(
|
||||
s: []const u8,
|
||||
chars: *const CharMap,
|
||||
line_height: c_long,
|
||||
x: f32,
|
||||
y: f32,
|
||||
scale: f32,
|
||||
line_max: f32,
|
||||
) !void {
|
||||
gl.useProgram(GlState.state.font_shader_program);
|
||||
gl.bindVertexArray(GlState.state.font_vao);
|
||||
|
||||
var cur_x = x;
|
||||
var cur_y: f32 = 0.0;
|
||||
|
||||
var char_iter = (try std.unicode.Utf8View.init(s)).iterator();
|
||||
while (char_iter.nextCodepoint()) |uchar| {
|
||||
const char = if (uchar > std.math.maxInt(u8)) '?' else @intCast(u8, uchar);
|
||||
|
||||
const ch = chars.get(char) orelse continue;
|
||||
|
||||
const xpos = cur_x + @intToFloat(f32, ch.bearing.x()) * scale;
|
||||
const ypos = y - (@intToFloat(f32, (ch.size.y() - ch.bearing.y())) + cur_y) * scale;
|
||||
|
||||
const w = @intToFloat(f32, ch.size.x()) * scale;
|
||||
const h = @intToFloat(f32, ch.size.y()) * scale;
|
||||
|
||||
// zig fmt: off
|
||||
const vertices = [_]f32{
|
||||
xpos, ypos + h, 0.0, 0.0,
|
||||
xpos, ypos, 0.0, 1.0,
|
||||
xpos + w, ypos, 1.0, 1.0,
|
||||
|
||||
xpos, ypos + h, 0.0, 0.0,
|
||||
xpos + w, ypos, 1.0, 1.0,
|
||||
xpos + w, ypos + h, 1.0, 0.0,
|
||||
};
|
||||
// zig fmt: on
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, ch.tex_id);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, GlState.state.font_vbo);
|
||||
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices.len * @sizeOf(f32), &vertices);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, vertices.len);
|
||||
|
||||
// bit shifting stuff is to convert to pixels
|
||||
cur_x += @intToFloat(f32, ch.advance >> 6) * scale;
|
||||
if (cur_x + @intToFloat(f32, ch.size.x()) >= line_max) {
|
||||
cur_x = x;
|
||||
cur_y += @intToFloat(f32, line_height) * scale;
|
||||
}
|
||||
}
|
||||
gl.bindTexture(gl.TEXTURE_2D, 0);
|
||||
}
|
9
src/font_frag.glsl
Normal file
9
src/font_frag.glsl
Normal file
|
@ -0,0 +1,9 @@
|
|||
#version 330 core
|
||||
in vec2 texCoords;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D text;
|
||||
|
||||
void main() {
|
||||
color = vec4(1.0, 1.0, 1.0, texture(text, texCoords).r);
|
||||
}
|
10
src/font_vert.glsl
Normal file
10
src/font_vert.glsl
Normal file
|
@ -0,0 +1,10 @@
|
|||
#version 330 core
|
||||
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
|
||||
out vec2 texCoords;
|
||||
|
||||
uniform mat4 projection;
|
||||
|
||||
void main() {
|
||||
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
|
||||
texCoords = vertex.zw;
|
||||
}
|
8
src/frag.glsl
Normal file
8
src/frag.glsl
Normal file
|
@ -0,0 +1,8 @@
|
|||
#version 330 core
|
||||
|
||||
in vec4 color;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = color;
|
||||
}
|
5055
src/gl.zig
Normal file
5055
src/gl.zig
Normal file
File diff suppressed because it is too large
Load diff
82
src/glutil.zig
Normal file
82
src/glutil.zig
Normal file
|
@ -0,0 +1,82 @@
|
|||
const std = @import("std");
|
||||
const c = @import("ffi.zig").c;
|
||||
const gl = @import("gl.zig");
|
||||
const Color = @import("Color.zig");
|
||||
const GlState = @import("GlState.zig");
|
||||
|
||||
pub fn rectangle(
|
||||
vertex_buf: *std.ArrayList(f32),
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
x2: f32,
|
||||
y2: f32,
|
||||
z: f32,
|
||||
color: Color,
|
||||
) !void {
|
||||
// TODO: indexed drawing?
|
||||
// zig fmt: off
|
||||
const vertices = [_]f32{
|
||||
x1, y1, z, color.r, color.g, color.b, color.a,
|
||||
x1, y2, z, color.r, color.g, color.b, color.a,
|
||||
x2, y1, z, color.r, color.g, color.b, color.a,
|
||||
|
||||
x1, y2, z, color.r, color.g, color.b, color.a,
|
||||
x2, y1, z, color.r, color.g, color.b, color.a,
|
||||
x2, y2, z, color.r, color.g, color.b, color.a,
|
||||
};
|
||||
// zig fmt: on
|
||||
|
||||
try vertex_buf.appendSlice(&vertices);
|
||||
}
|
||||
|
||||
pub fn copyVertices(vertices: []const f32) void {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, GlState.state.vbo);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
@intCast(isize, vertices.len * @sizeOf(f32)),
|
||||
vertices.ptr,
|
||||
gl.DYNAMIC_DRAW,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn drawTriangles(vertices: []const f32) void {
|
||||
gl.useProgram(GlState.state.shader_program);
|
||||
copyVertices(vertices);
|
||||
gl.bindVertexArray(GlState.state.vao);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, @intCast(c_int, vertices.len / 3));
|
||||
}
|
||||
|
||||
pub fn loadShader(src: [:0]const u8, t: c_uint) !c_uint {
|
||||
const id = gl.createShader(t);
|
||||
gl.shaderSource(
|
||||
id,
|
||||
1,
|
||||
&[_][*:0]const u8{src},
|
||||
&[_]c_int{@intCast(c_int, src.len)},
|
||||
);
|
||||
gl.compileShader(id);
|
||||
try checkShaderCompileError(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn checkShaderCompileError(shader: c_uint) !void {
|
||||
var success: c_int = 0;
|
||||
gl.getShaderiv(shader, gl.COMPILE_STATUS, &success);
|
||||
if (success == 0) {
|
||||
var info_buf = std.mem.zeroes([512:0]u8);
|
||||
gl.getShaderInfoLog(shader, 512, null, &info_buf);
|
||||
std.log.err("compiling shader: {s}", .{&info_buf});
|
||||
return error.ShaderCompileError;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkProgramLinkError(program: c_uint) !void {
|
||||
var success: c_int = 0;
|
||||
gl.getProgramiv(program, gl.LINK_STATUS, &success);
|
||||
if (success == 0) {
|
||||
var info_buf = std.mem.zeroes([512:0]u8);
|
||||
gl.getProgramInfoLog(program, 512, null, &info_buf);
|
||||
std.log.err("linking program: {s}", .{&info_buf});
|
||||
return error.ProgramError;
|
||||
}
|
||||
}
|
223
src/gui.zig
Normal file
223
src/gui.zig
Normal file
|
@ -0,0 +1,223 @@
|
|||
const std = @import("std");
|
||||
const zalgebra = @import("zalgebra");
|
||||
const c = @import("ffi.zig").c;
|
||||
const gl = @import("gl.zig");
|
||||
const glutil = @import("glutil.zig");
|
||||
const Color = @import("Color.zig");
|
||||
const GlState = @import("GlState.zig");
|
||||
const font = @import("font.zig");
|
||||
|
||||
// TODO: make these command line params
|
||||
const width = 4;
|
||||
const height = 4;
|
||||
|
||||
const square_margin = 0.1;
|
||||
|
||||
pub const State = struct {
|
||||
vertex_buf: std.ArrayList(f32),
|
||||
squares: [][]const u8,
|
||||
checked: []bool,
|
||||
chars: font.CharMap,
|
||||
line_height: c_long,
|
||||
prev_mouse_state: c_int = 0,
|
||||
|
||||
pub fn init(squares: [][]const u8, chars: font.CharMap, line_height: c_long) !State {
|
||||
return .{
|
||||
.vertex_buf = std.ArrayList(f32).init(std.heap.c_allocator),
|
||||
.squares = squares,
|
||||
.checked = try std.heap.c_allocator.alloc(bool, squares.len),
|
||||
.chars = chars,
|
||||
.line_height = line_height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *State) void {
|
||||
self.vertex_buf.deinit();
|
||||
std.heap.c_allocator.free(self.checked);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn handleInput(win: *c.GLFWwindow, state: *State) !void {
|
||||
if (c.glfwGetKey(win, c.GLFW_KEY_ESCAPE) == c.GLFW_PRESS or
|
||||
c.glfwGetKey(win, c.GLFW_KEY_Q) == c.GLFW_PRESS)
|
||||
{
|
||||
c.glfwSetWindowShouldClose(win, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const mouse_state = c.glfwGetMouseButton(win, c.GLFW_MOUSE_BUTTON_LEFT);
|
||||
if (state.prev_mouse_state != c.GLFW_PRESS and mouse_state == c.GLFW_PRESS) {
|
||||
std.log.debug("click", .{});
|
||||
var mousex: f64 = 0;
|
||||
var mousey: f64 = 0;
|
||||
|
||||
c.glfwGetCursorPos(win, &mousex, &mousey);
|
||||
|
||||
var norm_cursor_pos = font.current_projection_matrix.mulByVec4(
|
||||
zalgebra.Vec4.new(
|
||||
@floatCast(f32, mousex),
|
||||
@floatCast(f32, mousey),
|
||||
0.0,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
norm_cursor_pos.data[1] = -norm_cursor_pos.y();
|
||||
|
||||
const square_width = 2.0 / @intToFloat(f32, width);
|
||||
const square_height = 2.0 / @intToFloat(f32, height);
|
||||
|
||||
blk: {
|
||||
var x: u32 = 0;
|
||||
while (x < width) : (x += 1) {
|
||||
var y: u32 = 0;
|
||||
while (y < width) : (y += 1) {
|
||||
const square_x1 = ((@intToFloat(f32, x) + square_margin) / 2.0) - 1.0;
|
||||
const square_y1 = ((@intToFloat(f32, y) + square_margin) / 2.0) - 1.0;
|
||||
const square_x2 = square_x1 + square_width - square_margin;
|
||||
const square_y2 = square_y1 + square_height - square_margin;
|
||||
|
||||
if (norm_cursor_pos.x() >= square_x1 and
|
||||
norm_cursor_pos.x() <= square_x2 and
|
||||
norm_cursor_pos.y() >= square_y1 and
|
||||
norm_cursor_pos.y() <= square_y2)
|
||||
{
|
||||
state.checked[y * width + x] = !state.checked[y * width + x];
|
||||
|
||||
break :blk;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.prev_mouse_state != mouse_state) {
|
||||
state.prev_mouse_state = mouse_state;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(win: *c.GLFWwindow, state: *State) !void {
|
||||
_ = win;
|
||||
state.vertex_buf.clearRetainingCapacity();
|
||||
|
||||
gl.clearColor(
|
||||
Color.background.r,
|
||||
Color.background.g,
|
||||
Color.background.b,
|
||||
Color.background.a,
|
||||
);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
const square_width = 2.0 / @intToFloat(f32, width);
|
||||
const square_height = 2.0 / @intToFloat(f32, height);
|
||||
|
||||
// square frame
|
||||
{
|
||||
const color =
|
||||
Color{
|
||||
.r = Color.square_frame.r,
|
||||
.g = Color.square_frame.g,
|
||||
.b = Color.square_frame.b,
|
||||
.a = @sin(
|
||||
@floatCast(f32, c.glfwGetTime()),
|
||||
) / 2.0 + 0.5,
|
||||
};
|
||||
|
||||
var x: u32 = 0;
|
||||
while (x < width) : (x += 1) {
|
||||
var y: u32 = 0;
|
||||
while (y < width) : (y += 1) {
|
||||
if (!state.checked[y * width + x])
|
||||
continue;
|
||||
|
||||
const square_x = ((@intToFloat(f32, x) + square_margin - 0.02) / 2.0) -
|
||||
1.0;
|
||||
const square_y = ((@intToFloat(f32, y) + square_margin - 0.02) / 2.0) -
|
||||
1.0;
|
||||
try glutil.rectangle(
|
||||
&state.vertex_buf,
|
||||
square_x,
|
||||
square_y,
|
||||
square_x + square_width - square_margin + 0.02,
|
||||
square_y + square_height - square_margin + 0.02,
|
||||
-1.0,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// squares
|
||||
{
|
||||
var x: u32 = 0;
|
||||
while (x < width) : (x += 1) {
|
||||
var y: u32 = 0;
|
||||
while (y < width) : (y += 1) {
|
||||
const square_x = ((@intToFloat(f32, x) + square_margin) / 2.0) - 1.0;
|
||||
const square_y = ((@intToFloat(f32, y) + square_margin) / 2.0) - 1.0;
|
||||
try glutil.rectangle(
|
||||
&state.vertex_buf,
|
||||
square_x,
|
||||
square_y,
|
||||
square_x + square_width - square_margin,
|
||||
square_y + square_height - square_margin,
|
||||
0.0,
|
||||
Color.square_background,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glutil.drawTriangles(state.vertex_buf.items);
|
||||
|
||||
// Text
|
||||
{
|
||||
const scale = 0.4;
|
||||
|
||||
var x: u32 = 0;
|
||||
while (x < width) : (x += 1) {
|
||||
var y: u32 = 0;
|
||||
while (y < width) : (y += 1) {
|
||||
const pos = zalgebra.Vec4.new(
|
||||
((@intToFloat(f32, x) + square_margin) / 2.0) - 1.0,
|
||||
((@intToFloat(f32, y) + square_margin) / 2.0) - 1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
).add(
|
||||
zalgebra.Vec4.new(
|
||||
0.0,
|
||||
square_height - square_margin,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
);
|
||||
|
||||
const inv_proj_mtx = font.current_projection_matrix.inv();
|
||||
const screenpos = inv_proj_mtx.mulByVec4(pos).sub(
|
||||
zalgebra.Vec4.new(0.0, 48.0 * scale, 0.0, 0.0),
|
||||
);
|
||||
|
||||
const square_size = inv_proj_mtx.mulByVec4(
|
||||
pos.add(zalgebra.Vec4.new(
|
||||
square_width - square_margin,
|
||||
square_height - square_margin,
|
||||
0.0,
|
||||
0.0,
|
||||
)),
|
||||
);
|
||||
|
||||
try font.drawString(
|
||||
state.squares[y * width + x],
|
||||
&state.chars,
|
||||
state.line_height,
|
||||
screenpos.x(),
|
||||
screenpos.y(),
|
||||
scale,
|
||||
square_size.x(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/iosevka-regular.ttf
Normal file
BIN
src/iosevka-regular.ttf
Normal file
Binary file not shown.
153
src/main.zig
Normal file
153
src/main.zig
Normal file
|
@ -0,0 +1,153 @@
|
|||
const std = @import("std");
|
||||
const zalgebra = @import("zalgebra");
|
||||
const c = @import("ffi.zig").c;
|
||||
const gl = @import("gl.zig");
|
||||
const glutil = @import("glutil.zig");
|
||||
const gui = @import("gui.zig");
|
||||
const GlState = @import("GlState.zig");
|
||||
const bingoloader = @import("bingoloader.zig");
|
||||
const font = @import("font.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
if (std.os.argv.len != 2) {
|
||||
std.log.err(
|
||||
\\Invalid CLI usage
|
||||
\\
|
||||
\\Usage:
|
||||
\\{s} [bingolist]
|
||||
, .{std.os.argv[0]});
|
||||
return error.InvalidCLI;
|
||||
}
|
||||
|
||||
var bingos = try bingoloader.loadFile(std.mem.span(std.os.argv[1]), 16);
|
||||
defer bingos.deinit();
|
||||
|
||||
std.log.info("initializing GL state", .{});
|
||||
try GlState.init();
|
||||
defer GlState.deinit();
|
||||
|
||||
std.log.info("initializing GLFW", .{});
|
||||
if (c.glfwInit() == c.GLFW_FALSE) {
|
||||
return error.GlfwInit;
|
||||
}
|
||||
defer c.glfwTerminate();
|
||||
|
||||
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
// fancy transparent window
|
||||
c.glfwWindowHint(c.GLFW_TRANSPARENT_FRAMEBUFFER, c.GLFW_TRUE);
|
||||
|
||||
std.log.info("creating window", .{});
|
||||
const win = c.glfwCreateWindow(
|
||||
GlState.inital_win_size.x(),
|
||||
GlState.inital_win_size.y(),
|
||||
"Bingus",
|
||||
null,
|
||||
null,
|
||||
);
|
||||
if (win == null) {
|
||||
return error.GlfwCreateWindow;
|
||||
}
|
||||
|
||||
// create OpenGL context
|
||||
c.glfwMakeContextCurrent(win);
|
||||
|
||||
std.log.info("loading OpenGL", .{});
|
||||
try gl.load({}, getProcAddress);
|
||||
|
||||
// VSync
|
||||
c.glfwSwapInterval(1);
|
||||
|
||||
// window resize callback
|
||||
_ = c.glfwSetFramebufferSizeCallback(win, &fbResizeCb);
|
||||
|
||||
// mouse button buffering
|
||||
c.glfwSetInputMode(win, c.GLFW_STICKY_MOUSE_BUTTONS, c.GLFW_TRUE);
|
||||
|
||||
gl.viewport(0, 0, GlState.inital_win_size.x(), GlState.inital_win_size.y());
|
||||
|
||||
std.log.info("loading shaders", .{});
|
||||
const vert_shader = try glutil.loadShader(
|
||||
@embedFile("vert.glsl"),
|
||||
gl.VERTEX_SHADER,
|
||||
);
|
||||
const frag_shader = try glutil.loadShader(
|
||||
@embedFile("frag.glsl"),
|
||||
gl.FRAGMENT_SHADER,
|
||||
);
|
||||
|
||||
const shader_program = gl.createProgram();
|
||||
gl.attachShader(shader_program, vert_shader);
|
||||
gl.attachShader(shader_program, frag_shader);
|
||||
gl.linkProgram(shader_program);
|
||||
try glutil.checkProgramLinkError(shader_program);
|
||||
GlState.state.shader_program = shader_program;
|
||||
|
||||
// shaders can be delted once linked to a program
|
||||
gl.deleteShader(vert_shader);
|
||||
gl.deleteShader(frag_shader);
|
||||
|
||||
gl.genVertexArrays(1, &GlState.state.vao);
|
||||
gl.genBuffers(1, &GlState.state.vbo);
|
||||
|
||||
gl.bindVertexArray(GlState.state.vao);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, GlState.state.vbo);
|
||||
|
||||
// vertex pos
|
||||
gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 7 * @sizeOf(f32), null);
|
||||
gl.enableVertexAttribArray(0);
|
||||
|
||||
// vertex color
|
||||
gl.vertexAttribPointer(
|
||||
1,
|
||||
4,
|
||||
gl.FLOAT,
|
||||
gl.FALSE,
|
||||
7 * @sizeOf(f32),
|
||||
@intToPtr(*anyopaque, 3 * @sizeOf(f32)),
|
||||
);
|
||||
gl.enableVertexAttribArray(1);
|
||||
|
||||
try font.initFontGl();
|
||||
|
||||
//gl.enable(gl.CULL_FACE);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
var chars = font.CharMap.init(std.heap.c_allocator);
|
||||
defer chars.deinit();
|
||||
var line_height: c_long = 0;
|
||||
try font.load(&chars, &line_height);
|
||||
|
||||
//gl.polygonMode(gl.FRONT_AND_BACK, gl.LINE);
|
||||
|
||||
var gui_state = try gui.State.init(bingos.bingos, chars, line_height);
|
||||
defer gui_state.deinit();
|
||||
while (c.glfwWindowShouldClose(win) == 0) {
|
||||
c.glfwPollEvents();
|
||||
defer c.glfwSwapBuffers(win);
|
||||
|
||||
try gui.handleInput(win.?, &gui_state);
|
||||
|
||||
if (c.glfwGetWindowAttrib(win, c.GLFW_VISIBLE) == 0) {
|
||||
// window isn't visible, so no need to do any rendering
|
||||
continue;
|
||||
}
|
||||
|
||||
try gui.draw(win.?, &gui_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn getProcAddress(_: void, name: [:0]const u8) ?gl.FunctionPointer {
|
||||
return c.glfwGetProcAddress(name.ptr);
|
||||
}
|
||||
|
||||
fn fbResizeCb(win: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
|
||||
_ = win;
|
||||
const vec = zalgebra.Vec2_i32.new(width, height);
|
||||
gl.viewport(0, 0, width, height);
|
||||
font.updateProgramProjectionMatrix(vec);
|
||||
GlState.state.current_screen_size = vec;
|
||||
}
|
11
src/vert.glsl
Normal file
11
src/vert.glsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec4 vColor;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
color = vColor;
|
||||
}
|
1
zalgebra
Submodule
1
zalgebra
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 3c246c622598417c849a5c7425b8df211bb443b3
|
Loading…
Reference in a new issue