feat: port GUI to boxflow layout engine

This commit is contained in:
LordMZTE 2022-12-16 21:14:19 +01:00
parent 727054ed4c
commit effa34f7d1
Signed by: LordMZTE
GPG Key ID: B64802DC33A64FF6
8 changed files with 175 additions and 196 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "zalgebra"]
path = zalgebra
url = https://github.com/kooparse/zalgebra.git
[submodule "boxflow"]
path = boxflow
url = https://mzte.de/git/LordMZTE/boxflow

1
boxflow Submodule

@ -0,0 +1 @@
Subproject commit 22fc38acd8c4a2b74676e25b4c95c3a763c93cb4

View File

@ -45,6 +45,11 @@ fn addDeps(exe: *std.build.LibExeObjStep) void {
.source = .{ .path = "zalgebra/src/main.zig" },
});
exe.addPackage(.{
.name = "boxflow",
.source = .{ .path = "boxflow/src/main.zig" },
});
exe.linkLibC();
exe.linkSystemLibrary("glfw3");
exe.linkSystemLibrary("freetype2");

View File

@ -1,4 +1,5 @@
const std = @import("std");
const gl = @import("gl.zig");
const zalgebra = @import("zalgebra");
pub const inital_win_size = zalgebra.Vec2_i32.new(800, 800);
@ -9,6 +10,8 @@ pub var state: Self = undefined;
current_screen_size: zalgebra.Vec2_i32 = inital_win_size,
projection_matrix: zalgebra.Mat4 = undefined,
vbo: c_uint = 0,
vao: c_uint = 0,
@ -28,3 +31,24 @@ pub fn init() !void {
pub fn deinit() void {
state = undefined;
}
pub fn updateProjectionMatrix(size: zalgebra.Vec2_i32) void {
state.projection_matrix = zalgebra.Mat4.orthographic(
0.0,
@intToFloat(f32, size.x()),
0.0,
@intToFloat(f32, size.y()),
-1.0,
1.0,
);
for ([_]c_uint{ state.shader_program, state.font_shader_program }) |program| {
gl.useProgram(program);
gl.uniformMatrix4fv(
gl.getUniformLocation(program, "projection"),
1,
gl.FALSE,
@ptrCast([*]const f32, std.mem.asBytes(&state.projection_matrix.data)),
);
}
}

View File

@ -32,8 +32,6 @@ pub const CharMap = std.AutoHashMap(u8, Character);
// TODO: use fontconfig to find system font?
const font_data = @embedFile("iosevka-regular.ttf");
pub var current_projection_matrix: zalgebra.Mat4 = undefined;
pub fn load() !FontInfo {
std.log.info("loading font", .{});
var freetype: c.FT_Library = undefined;
@ -131,8 +129,6 @@ pub fn initFontGl() !void {
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);
@ -143,43 +139,6 @@ pub fn initFontGl() !void {
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 textVertices(
s: []const u8,
info: *const FontInfo,

View File

@ -1,5 +1,6 @@
const std = @import("std");
const zalgebra = @import("zalgebra");
const boxflow = @import("boxflow");
const c = @import("ffi.zig").c;
const gl = @import("gl.zig");
const glutil = @import("glutil.zig");
@ -7,34 +8,111 @@ const Color = @import("Color.zig");
const GlState = @import("GlState.zig");
const font = @import("font.zig");
const BingoBox = struct {
checked: bool = false,
b: boxflow.boxes.Simple = .{},
text: []const u8,
pub fn render(
self: *BingoBox,
state: *State,
) !void {
if (self.checked) {
try glutil.rectangle(
&state.vertex_buf,
@intToFloat(f32, self.b.data.pos.x),
@intToFloat(
f32,
GlState.state.current_screen_size.y() -
@intCast(i32, self.b.data.pos.y),
),
@intToFloat(f32, self.b.data.pos.x + self.b.data.size.width),
@intToFloat(
f32,
GlState.state.current_screen_size.y() -
@intCast(i32, self.b.data.pos.y + self.b.data.size.height),
),
0.0,
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,
},
);
}
try glutil.rectangle(
&state.vertex_buf,
@intToFloat(f32, self.b.data.pos.x + 6),
@intToFloat(
f32,
GlState.state.current_screen_size.y() -
@intCast(i32, self.b.data.pos.y) - 6,
),
@intToFloat(f32, (self.b.data.pos.x + self.b.data.size.width) -| 6),
@intToFloat(
f32,
GlState.state.current_screen_size.y() -
@intCast(i32, (self.b.data.pos.y + self.b.data.size.height) -| 6),
),
0.0,
Color.square_background,
);
try font.textVertices(
self.text,
&state.font_info,
&state.font_vertex_buf,
@intToFloat(f32, self.b.data.pos.x + 6),
@intToFloat(
f32,
GlState.state.current_screen_size.y() -
@intCast(i32, self.b.data.pos.y) - 6,
) - 48.0 * 0.4,
0.4,
@intToFloat(f32, self.b.data.pos.x + self.b.data.size.width),
);
}
};
// 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),
font_vertex_buf: std.ArrayList(f32),
squares: [][]const u8,
checked: []bool,
boxes: []BingoBox,
font_info: font.FontInfo,
prev_mouse_state: c_int = 0,
debug_render: bool = false,
pub fn init(squares: [][]const u8) !State {
const checked = try std.heap.c_allocator.alloc(bool, squares.len);
std.mem.set(bool, checked, false);
const boxes = try std.heap.c_allocator.alloc(BingoBox, width * height);
errdefer std.heap.c_allocator.free(boxes);
for (boxes) |*b, i| {
b.* = BingoBox{
.text = squares[i],
};
}
return .{
.vertex_buf = std.ArrayList(f32).init(std.heap.c_allocator),
.font_vertex_buf = std.ArrayList(f32).init(std.heap.c_allocator),
.squares = squares,
.checked = checked,
.boxes = boxes,
.font_info = try font.load(),
};
}
pub fn deinit(self: *State) void {
self.vertex_buf.deinit();
std.heap.c_allocator.free(self.checked);
std.heap.c_allocator.free(self.boxes);
self.font_info.deinit();
self.* = undefined;
@ -59,45 +137,20 @@ pub fn handleInput(win: *c.GLFWwindow, state: *State) !void {
// TODO: convert to event-based system
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) {
var mousex: f64 = 0;
var mousey: f64 = 0;
var fmousex: f64 = 0;
var fmousey: f64 = 0;
c.glfwGetCursorPos(win, &mousex, &mousey);
c.glfwGetCursorPos(win, &fmousex, &fmousey);
var norm_cursor_pos = font.current_projection_matrix.mulByVec4(
zalgebra.Vec4.new(
@floatCast(f32, mousex),
@floatCast(f32, mousey),
0.0,
1.0,
),
);
const mousex = @floatToInt(usize, fmousex);
const mousey = @floatToInt(usize, fmousey);
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;
}
}
for (state.boxes) |*b| {
if (mousey > b.b.data.pos.y and mousey < b.b.data.pos.y + b.b.data.size.width and
mousex > b.b.data.pos.x and mousex < b.b.data.pos.x + b.b.data.size.height)
{
b.checked = !b.checked;
break;
}
}
}
@ -107,9 +160,32 @@ pub fn handleInput(win: *c.GLFWwindow, state: *State) !void {
}
}
pub fn layout(state: *State, w: c_int, h: c_int) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
defer arena.deinit();
const alloc = arena.allocator();
const gridboxes = try alloc.alloc(boxflow.Box, state.boxes.len);
for (state.boxes) |*b, i| {
var padding = try alloc.create(boxflow.boxes.Padding);
padding.* = boxflow.boxes.Padding{ .child = b.b.box(), .padding = 4 };
gridboxes[i] = padding.box();
}
var grid = boxflow.boxes.FixedGrid{ .cols = width, .children = gridboxes };
var root = boxflow.Root{ .root_box = grid.box(), .size = .{
.width = @intCast(usize, w),
.height = @intCast(usize, h),
} };
_ = try root.layout();
}
pub fn draw(win: *c.GLFWwindow, state: *State) !void {
_ = win;
state.vertex_buf.clearRetainingCapacity();
state.font_vertex_buf.clearRetainingCapacity();
gl.clearColor(
Color.background.r,
@ -119,118 +195,12 @@ pub fn draw(win: *c.GLFWwindow, state: *State) !void {
);
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,
);
}
}
for (state.boxes) |*b| {
try b.render(state);
}
glutil.drawTriangles(state.vertex_buf.items);
state.vertex_buf.clearRetainingCapacity();
// 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.textVertices(
state.squares[y * width + x],
&state.font_info,
&state.vertex_buf,
screenpos.x(),
screenpos.y(),
scale,
square_size.x(),
);
}
}
font.renderText(state.vertex_buf.items, &state.font_info);
}
font.renderText(state.font_vertex_buf.items, &state.font_info);
// debug stuff
if (state.debug_render) {

View File

@ -123,6 +123,8 @@ pub fn main() !void {
try font.initFontGl();
GlState.updateProjectionMatrix(GlState.inital_win_size);
//gl.enable(gl.CULL_FACE);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
@ -131,6 +133,11 @@ pub fn main() !void {
var gui_state = try gui.State.init(bingos.bingos);
defer gui_state.deinit();
try gui.layout(
&gui_state,
GlState.inital_win_size.x(),
GlState.inital_win_size.y(),
);
c.glfwSetWindowUserPointer(win, &gui_state);
while (c.glfwWindowShouldClose(win) == 0) {
c.glfwPollEvents();
@ -172,9 +179,17 @@ fn keyEventCb(
}
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.updateProjectionMatrix(vec);
GlState.state.current_screen_size = vec;
const state = @ptrCast(
*gui.State,
@alignCast(@alignOf(*gui.State), c.glfwGetWindowUserPointer(win)),
);
gui.layout(state, width, height) catch |e| {
std.log.err("layout: {}", .{e});
};
}

View File

@ -3,9 +3,11 @@
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec4 vColor;
uniform mat4 projection;
out vec4 color;
void main() {
gl_Position = vec4(aPos, 1.0);
gl_Position = projection * vec4(aPos, 1.0);
color = vColor;
}