From 8ddd30ee885b65fe106239a70269edb0ab3d5e3a Mon Sep 17 00:00:00 2001 From: Patrick Cleavelin Date: Wed, 21 Jun 2023 21:42:03 -0500 Subject: [PATCH] big boi inital commit, barely have rendered ui --- .gitmodules | 3 + build.zig | 74 ++++++++++ libs/raylib | 1 + src/main.zig | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+) create mode 100644 .gitmodules create mode 100644 build.zig create mode 160000 libs/raylib create mode 100644 src/main.zig diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e746f0d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/raylib"] + path = libs/raylib + url = https://github.com/ryupold/raylib.zig diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..869b8f5 --- /dev/null +++ b/build.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const raylib = @import("libs/raylib/build.zig"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) 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 optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zooy", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // Add raylib + raylib.addTo(b, exe, target, optimize); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/libs/raylib b/libs/raylib new file mode 160000 index 0000000..8575074 --- /dev/null +++ b/libs/raylib @@ -0,0 +1 @@ +Subproject commit 85750745b3a621e2107083e0589cf5fc3a1a73e7 diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..5f9ae94 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,371 @@ +const std = @import("std"); +const raylib = @import("raylib"); + +const UI_Flags = enum(u32) { + nothing = 0, + clickable = (1 << 0), + drawText = (1 << 1), + drawBorder = (1 << 2), +}; + +const Vec2 = struct { + x: f32, + y: f32, +}; + +/// the most (and only) basic primitive +const UI_Box = struct { + /// the first child + first: ?*UI_Box, + last: ?*UI_Box, + + /// the next sibling + next: ?*UI_Box, + prev: ?*UI_Box, + + parent: ?*UI_Box, + + /// the assigned features + flags: u32, + + /// the label? + label: [:0]const u8, + + /// the final computed position and size of this primitive + computed_pos: Vec2, + computed_size: Vec2, +}; + +var box_allocator: std.mem.Allocator = undefined; +var root_box: ?*UI_Box = null; +var current_box: ?*UI_Box = null; +var pushing_box: bool = false; +var popping_box: bool = false; + +fn DeleteBoxChildren(box: *UI_Box, should_destroy: bool) void { + if (box.first) |child| { + DeleteBoxChildren(child, true); + } else if (should_destroy) { + box_allocator.destroy(box); + } +} + +fn MakeBox(label: [:0]const u8, flags: UI_Flags) anyerror!*UI_Box { + std.debug.print("making box '{s}'...", .{label}); + popping_box = false; + + if (pushing_box) { + const box = try PushBox(label, flags); + pushing_box = false; + + return box; + } + + if (current_box) |box| { + if (box.next) |next| { + // Attempt to re-use cache + if (std.mem.eql(u8, next.label, label)) { + std.debug.print("using cache for '{s}'\n", .{next.label}); + next.flags = @enumToInt(flags); + if (next.parent) |parent| { + parent.last = next; + } + current_box = next; + return next; + } else { + // Invalid cache, delete next sibling while retaining the following one + std.debug.print("make_box: invalidating cache for '{s}' when making new box '{s}'\n", .{ next.label, label }); + const following_sibling = next.next; + DeleteBoxChildren(next, false); + + next.* = UI_Box{ + .label = label, + .flags = @enumToInt(flags), + + .first = null, + .last = null, + .next = following_sibling, + .prev = null, + .parent = box.parent, + .computed_pos = Vec2{ .x = 0, .y = 0 }, + .computed_size = Vec2{ .x = 0, .y = 0 }, + }; + + current_box = next; + if (next.parent) |parent| { + parent.last = next; + } + + return next; + } + } else { + // No existing cache, create new box + std.debug.print("make_box: allocating new box: {s}\n", .{label}); + var new_box = try box_allocator.create(UI_Box); + new_box.* = UI_Box{ + .label = label, + .flags = @enumToInt(flags), + + .first = null, + .last = null, + .next = null, + .prev = null, + .parent = box.parent, + .computed_pos = Vec2{ .x = 0, .y = 0 }, + .computed_size = Vec2{ .x = 0, .y = 0 }, + }; + + box.next = new_box; + current_box = new_box; + if (new_box.parent) |parent| { + parent.last = new_box; + } + + return new_box; + } + } else { + std.debug.print("make_box: allocating new box: {s}\n", .{label}); + var new_box = try box_allocator.create(UI_Box); + new_box.* = UI_Box{ + .label = label, + .flags = @enumToInt(flags), + + .first = null, + .last = null, + .next = null, + .prev = null, + .parent = null, + .computed_pos = Vec2{ .x = 0, .y = 0 }, + .computed_size = Vec2{ .x = 0, .y = 0 }, + }; + + current_box = new_box; + return new_box; + } +} + +fn PushBox(label: [:0]const u8, flags: UI_Flags) anyerror!*UI_Box { + std.debug.print("pushing box '{s}'...", .{label}); + + if (popping_box) { + const box = try MakeBox(label, flags); + pushing_box = true; + + return box; + } + if (!pushing_box) { + pushing_box = true; + } + + if (current_box) |box| { + // Attempt to re-use cache + if (box.first) |first| { + // check if the same + if (std.mem.eql(u8, first.label, label)) { + std.debug.print("using cache for '{s}'\n", .{first.label}); + first.flags = @enumToInt(flags); + current_box = first; + + if (first.parent) |parent| { + parent.last = first; + } + return first; + } else { + // Invalid cache + std.debug.print("push_box: invalidating cache for '{s}' when making new box '{s}'\n", .{ first.label, label }); + const following_sibling = first.next; + DeleteBoxChildren(first, false); + + first.* = UI_Box{ + .label = label, + .flags = @enumToInt(flags), + + .first = null, + .last = null, + .next = following_sibling, + .prev = null, + .parent = current_box, + .computed_pos = Vec2{ .x = 0, .y = 0 }, + .computed_size = Vec2{ .x = 0, .y = 0 }, + }; + + current_box = first; + if (first.parent) |parent| { + parent.last = first; + } + return first; + } + } else { + std.debug.print("push_box: allocating new box: {s}\n", .{label}); + var new_box = try box_allocator.create(UI_Box); + new_box.* = UI_Box{ + .label = label, + .flags = @enumToInt(flags), + + .first = null, + .last = null, + .next = null, + .prev = null, + .parent = current_box, + .computed_pos = Vec2{ .x = 0, .y = 0 }, + .computed_size = Vec2{ .x = 0, .y = 0 }, + }; + + box.first = new_box; + current_box = new_box; + if (new_box.parent) |parent| { + parent.last = new_box; + } + return new_box; + } + } else { + pushing_box = false; + return try MakeBox(label, flags); + } +} + +fn PopBox() void { + std.debug.print("popping box...", .{}); + if (current_box) |box| { + //if (box.parent) |parent| { + //current_box = parent.last; + //return; + //} + current_box = box.parent; + popping_box = true; + return; + } + + std.debug.print("couldn't pop box\n", .{}); +} + +fn TestBoxClick(box: *UI_Box, mouse_x: f32, mouse_y: f32, mouse_clicked: bool) bool { + return mouse_clicked and mouse_x >= box.computed_pos.x and mouse_x <= box.computed_pos.x + box.computed_size.x and mouse_y >= box.computed_pos.y and mouse_y <= box.computed_pos.y + box.computed_size.y; +} + +fn MakeButton(label: [:0]const u8) !bool { + var box = try MakeBox(label, UI_Flags.clickable); + + const mouse_x = 0; + const mouse_y = 0; + const mouse_clicked = false; + + return TestBoxClick(box, mouse_x, mouse_y, mouse_clicked); +} + +fn CountChildren(box: *UI_Box) u32 { + var count: u32 = 0; + var b = box.first; + + while (b) |child| { + count += 1; + + b = child.next; + } + + return count; +} + +fn CountSiblings(box: *UI_Box) u32 { + var count: u32 = 0; + var b = box; + + while (b.next) |next| { + count += 1; + + b = next; + } + + return count; +} + +fn DrawUI(box: *UI_Box, parent: ?*UI_Box, parent_pos: Vec2, parent_size: Vec2) void { + //DrawRectangle(int posX, int posY, int width, int height, Color color + + std.debug.print("\n\ndrawing {s}\n", .{box.label}); + + const num_siblings = if (parent) |p| (CountChildren(p) - 1) else 0; + std.debug.print("num_siblings {d}\n", .{num_siblings}); + + const num_children = CountChildren(box); + std.debug.print("num_children {d}\n", .{num_children}); + + const num_siblings_after_me = CountSiblings(box); + std.debug.print("num_siblings_after_me {d}\n", .{num_siblings_after_me}); + + const my_index = num_siblings - num_siblings_after_me; + std.debug.print("num_index {d}\n", .{my_index}); + + box.computed_size = Vec2{ + .x = parent_size.x / (@intToFloat(f32, num_siblings) + 1), + .y = parent_size.y, + //.y = parent_size.y / (@intToFloat(f32, num_siblings) + 1), + }; + box.computed_pos = Vec2{ + .x = box.computed_size.x * @intToFloat(f32, my_index) + parent_pos.x, + .y = parent_pos.y + 12, + //.y = box.computed_size.y * @intToFloat(f32, my_index) + parent_pos.y, + }; + + raylib.DrawRectangleLines(@floatToInt(i32, box.computed_pos.x), @floatToInt(i32, box.computed_pos.y), @floatToInt(i32, box.computed_size.x), @floatToInt(i32, box.computed_size.y), raylib.BLUE); + raylib.DrawText(box.label, @floatToInt(i32, box.computed_pos.x), @floatToInt(i32, box.computed_pos.y), 10, raylib.RED); + + // draw children + var child = box.first; + while (child) |c| { + DrawUI(c, box, box.computed_pos, box.computed_size); + + child = c.next; + } +} + +pub fn main() !void { + raylib.InitWindow(800, 600, "Zooy Test"); + raylib.SetConfigFlags(raylib.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); + raylib.SetTargetFPS(60); + + defer raylib.CloseWindow(); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + box_allocator = gpa.allocator(); + + root_box = try PushBox("RootContainer", UI_Flags.nothing); + + std.debug.print("Starting main loop\n", .{}); + var ran = false; + while (!raylib.WindowShouldClose()) { + current_box = root_box; + pushing_box = false; + popping_box = false; + + raylib.BeginDrawing(); + defer raylib.EndDrawing(); + + raylib.ClearBackground(raylib.BLACK); + + _ = try PushBox("ButtonArray", UI_Flags.nothing); + if (try MakeButton("click me")) { + std.debug.print("button clicked", .{}); + } + if (try MakeButton("click me 2")) { + std.debug.print("button 2 clicked", .{}); + } + PopBox(); + + _ = try PushBox("TextArray", UI_Flags.nothing); + _ = try MakeBox("This is some text", UI_Flags.nothing); + _ = try MakeBox("So is this", UI_Flags.nothing); + PopBox(); + + if (root_box) |box| { + DrawUI(box, null, .{ .x = 0, .y = 0 }, .{ .x = 800, .y = 600 }); + } + + // raylib.DrawFPS(10, 10); + + //raylib.DrawText("Hello Zooy", 100, 100, 20, raylib.YELLOW); + + //if (ran) break; + ran = true; + } +}