zooy/src/main.zig

381 lines
11 KiB
Zig

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;
var mouse_x: i32 = 0;
var mouse_y: i32 = 0;
var mouse_released: 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!bool {
//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;
} 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;
}
}
} 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;
}
}
} 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;
}
if (current_box) |box| {
if (box.flags & @enumToInt(UI_Flags.clickable) > 0) {
return TestBoxClick(box);
}
}
return false;
}
fn PushBox(label: [:0]const u8, flags: UI_Flags) anyerror!bool {
//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;
}
} 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;
}
}
} 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;
}
}
} else {
pushing_box = false;
return try MakeBox(label, flags);
}
if (current_box) |box| {
if (box.flags & @enumToInt(UI_Flags.clickable) > 0) {
return TestBoxClick(box);
}
}
return false;
}
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) bool {
return mouse_released and @intToFloat(f32, mouse_x) >= box.computed_pos.x and @intToFloat(f32, mouse_x) <= box.computed_pos.x + box.computed_size.x and @intToFloat(f32, mouse_y) >= box.computed_pos.y and @intToFloat(f32, mouse_y) <= box.computed_pos.y + box.computed_size.y;
}
fn MakeButton(label: [:0]const u8) !bool {
return try MakeBox(label, UI_Flags.clickable);
}
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();
_ = try PushBox("RootContainer", UI_Flags.nothing);
root_box = current_box;
//std.debug.print("Starting main loop\n", .{});
var ran = false;
while (!raylib.WindowShouldClose()) {
current_box = root_box;
pushing_box = false;
popping_box = false;
mouse_x = raylib.GetMouseX();
mouse_y = raylib.GetMouseY();
mouse_released = raylib.IsMouseButtonReleased(raylib.MouseButton.MOUSE_BUTTON_LEFT);
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\n", .{});
}
if (try MakeButton("click me 2")) {
std.debug.print("button 2 clicked\n", .{});
}
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;
}
}