From f9268bf9ff4cb6df6ad6117c24bfa3a4715b8755 Mon Sep 17 00:00:00 2001 From: sirlilpanda Date: Fri, 16 Jan 2026 16:24:00 +1300 Subject: [PATCH] basic raylib example for the ui --- examples/raylib-example/build.zig | 51 +++ examples/raylib-example/build.zig.zon | 20 ++ examples/raylib-example/src/main.zig | 446 ++++++++++++++++++++++++++ 3 files changed, 517 insertions(+) create mode 100644 examples/raylib-example/build.zig create mode 100644 examples/raylib-example/build.zig.zon create mode 100644 examples/raylib-example/src/main.zig diff --git a/examples/raylib-example/build.zig b/examples/raylib-example/build.zig new file mode 100644 index 0000000..d6ca421 --- /dev/null +++ b/examples/raylib-example/build.zig @@ -0,0 +1,51 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const raylib_dep = b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, + }); + const raylib_artifact = raylib_dep.artifact("raylib"); // raylib C library + + const shoots = b.dependency("shoots", .{ + .target = target, + .optimize = optimize, + }); + const exe = b.addExecutable(.{ + .name = "examples", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + exe.linkLibrary(raylib_artifact); + exe.root_module.addImport("shoots", shoots.module("shoots")); + exe.root_module.addImport("rl", raylib_dep.module("raylib")); + + b.installArtifact(exe); + + const run_step = b.step("run", "Run the app"); + + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const exe_tests = b.addTest(.{ + .root_module = exe.root_module, + }); + + const run_exe_tests = b.addRunArtifact(exe_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_exe_tests.step); +} diff --git a/examples/raylib-example/build.zig.zon b/examples/raylib-example/build.zig.zon new file mode 100644 index 0000000..5fe0139 --- /dev/null +++ b/examples/raylib-example/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .examples, + .version = "0.0.0", + .fingerprint = 0x7bd0ad45c350042a, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + .raylib_zig = .{ + .url = "git+https://github.com/raylib-zig/raylib-zig?ref=devel#a4d18b2d1cf8fdddec68b5b084535fca0475f466", + .hash = "raylib_zig-5.6.0-dev-KE8REL5MBQAf3p497t52Xw9P7ojndIkVOWPXnLiLLw2P", + }, + .shoots = .{ + .path = "../", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/examples/raylib-example/src/main.zig b/examples/raylib-example/src/main.zig new file mode 100644 index 0000000..8f3928a --- /dev/null +++ b/examples/raylib-example/src/main.zig @@ -0,0 +1,446 @@ +const std = @import("std"); +const rl = @import("rl"); +const shoots = @import("shoots"); + +fn getTextWidth(string: []const u8, font: rl.Font) shoots.Real { + var width: c_int = 0; + for (string) |char| { + width += + @as(c_int, @intFromFloat(font.recs[char - 32].width)) + + font.glyphs[char - 32].offsetX; + } + + const s = @as([:0]const u8, @ptrCast(string)); + return @as(shoots.Real, @floatFromInt(rl.measureText(s, font.baseSize))); +} + +fn getTextheight(string: []const u8, font: rl.Font) shoots.Real { + _ = string; + // std.debug.print("font.baseSize : {}\n", .{font.baseSize}); + return @as(shoots.Real, @floatFromInt(font.baseSize)); +} + +const TextType = shoots.TextType(rl.Font, getTextWidth, getTextheight); +const TextureType = shoots.TextureType(rl.Texture2D); + +const UI = shoots.Shoots(TextType, TextureType); + +inline fn sideBar() UI.Node { + return UI.Element(.{ + .name = "sidebar", + .rect = .{ + .h = 20, + .w = 40, + }, + .style = .{ + .background_colour = .{ + .r = 255, + .b = 255, + }, + .size_y = .grow, + // .size_x = .grow, + }, + }); +} + +fn updateButton(element: *UI.Ele, mouse_data: shoots.MouseState, user_data: *anyopaque) bool { + _ = user_data; + + if (mouse_data.left) { + std.debug.print("button pressed\n", .{}); + + element.style.background_colour = .{ .b = 255, .g = 255 }; + } + + return false; +} + +fn hoverButton(element: *UI.Ele, mouse_data: shoots.MouseState, user_data: *anyopaque) bool { + _ = user_data; + _ = mouse_data; + + element.style.background_colour = .{ .b = 128, .g = 128 }; + + return false; +} + +inline fn button() UI.Node { + return UI.ElementWborder( + .{ + .bottom = 2, + .left = 2, + .right = 2, + .top = 2, + }, + .{ + .b = 30, + }, + .{ + .name = "button", + .rect = .{ + .h = 20, + .w = 60, + }, + .on_click = .{ + .func = &updateButton, + .data = &.{}, + }, + .on_hover = .{ + .func = &hoverButton, + .data = &.{}, + }, + .style = .{ + .background_colour = .{ + .g = 255, + }, + .rounded = 25, + }, + }, + ); +} + +pub fn main() anyerror!void { + // Initialization + //-------------------------------------------------------------------------------------- + const screenWidth = 1200; + const screenHeight = 700; + var timer = try std.time.Timer.start(); + + rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - basic window"); + defer rl.closeWindow(); // Close window and OpenGL context + + rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + std.debug.print("shoots version :{s}\n", .{shoots.VERSION}); + std.debug.print("raylib time : {}\n", .{@divTrunc(timer.lap(), std.time.ns_per_ms)}); + + const child = &[_]UI.Node{ + sideBar(), + button(), + UI.Element(.{ + .style = .{ + .background_colour = .{ + .b = 120, + }, + .padding = .{ + .bottom = 10, + .left = 10, + .right = 10.4, + .top = 10, + }, + }, + .children = &[_]UI.Node{ + UI.Txt( + UI.Text.init( + "hello world", + try rl.getFontDefault(), + .{}, + ), + ), + UI.Txt( + UI.Text.init( + "hello world again", + try rl.getFontDefault(), + .{}, + ).setBackground(.{ + .a = 255, + .g = 255, + }), + ), + }, + }), + + UI.Element(.{ + .name = "child", + .style = .{ .background_colour = .{ + .b = 255, + }, .padding = .{ .bottom = 5 } }, + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch1", + // .rect = .{ .w = 200 }, + .style = .{ + .size_x = .grow, + .padding = .{ + .bottom = 5, + .left = 5, + .top = 5, + .right = 5, + }, + // .layout = .right_to_left, + .child_gap = 5, + }, + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch11", + .style = .{ + .background_colour = .{ + .g = 128, + .b = 128, + .r = 128, + }, + .size_x = .grow, + }, + .rect = .{ + .h = 40, + .w = 40, + }, + }), + UI.Element(.{ + .name = "branch12", + .style = .{ + .background_colour = .{ + .g = 128, + .r = 128, + }, + .size_x = .grow, + }, + .rect = .{ + .h = 40, + .w = 40, + }, + }), + UI.Element(.{ + .name = "branch13", + .rect = .{ + .h = 40, + .w = 40, + }, + .style = .{ + .background_colour = .{ + .g = 90, + .b = 150, + }, + }, + }), + }, + }), + UI.Element(.{ + .name = "branch2", + .style = .{ + .background_colour = .{ + .r = 255, + .g = 128, + }, + .layout = .right_to_left, + .child_gap = 4, + .padding = .{ + .left = 10, + .bottom = 10, + .right = 10, + .top = 10, + }, + }, + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch21", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 255, + .b = 150, + }, + }, + }), + UI.Element(.{ + .name = "branch22", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 90, + .b = 150, + }, + }, + }), + UI.Element(.{ + .name = "branch21", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 255, + .b = 150, + }, + }, + }), + UI.Element(.{ + .name = "branch22", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 90, + .b = 150, + }, + }, + }), + UI.Element(.{ + .name = "branch21", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 255, + .b = 150, + }, + }, + }), + UI.Element(.{ + .name = "branch22", + .rect = .{ + .h = 30, + .w = 30, + }, + .style = .{ + .rounded = 40, + .background_colour = .{ + .r = 90, + .b = 150, + }, + }, + }), + }, + }), + }, + }), + button(), + button(), + }; + + const root = UI.Element(.{ + .name = "root", + .pos = .{ + .x = 0, + .y = 0, + }, + .rect = .{ + .h = screenHeight, + .w = screenWidth, + }, + // .on_hover = callback(), + // .on_click = callback(), + .style = .{ + .background_colour = .{ + .r = 255, + }, + .layout = .right_to_left, + // .size_x = .fit(), + // .size_y = .fixed(), + }, + .children = child, + }); + + // const arena = [1024]u8{0}; + + var al = std.heap.ArenaAllocator.init(std.heap.smp_allocator); + std.debug.print("root : {f}\n", .{root}); + // UI.printTree(sized); + // std.debug.print("layout time : {}\n", .{@divTrunc(timer.lap(), std.time.ns_per_ms)}); + + // Main game loop + while (!rl.windowShouldClose()) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + rl.beginDrawing(); + defer rl.endDrawing(); + // std.debug.print("starting layout\n", .{}); + _ = timer.lap(); + var sized = try UI.resolveSizing(al.allocator(), root); + + const mouse_pos = rl.getMousePosition(); + + _ = UI.processInteractions(&sized, .{ + .pos = .{ + .x = mouse_pos.x, + .y = mouse_pos.y, + }, + .left = rl.isMouseButtonDown(.left), + .middle = rl.isMouseButtonDown(.middle), + .right = rl.isMouseButtonDown(.right), + }); + + const commands = try UI.getRenderCommands(sized, al.allocator()); + // std.debug.print("layout time : {}ms\n", .{ + // @as(f32, @floatFromInt(timer.lap())) / @as(f32, @floatFromInt(std.time.ns_per_ms)), + // }); + + for (commands.items) |command| { + // std.debug.print("command : {any}\n", .{command}); + switch (command) { + .rect => |r| { + if (r.rounding) |rounding| { + rl.drawRectangleRounded( + rl.Rectangle.init( + r.pos.x, + r.pos.y, + r.rect.w, + r.rect.h, + ), + rounding / 100.0, + 10, + rl.Color.init( + r.colour.r, + r.colour.g, + r.colour.b, + r.colour.a, + ), + ); + } else { + rl.drawRectangleRec(rl.Rectangle.init( + r.pos.x, + r.pos.y, + r.rect.w, + r.rect.h, + ), rl.Color.init( + r.colour.r, + r.colour.g, + r.colour.b, + r.colour.a, + )); + } + }, + .text => |t| { + const string = @as([:0]const u8, @ptrCast(t.text.string)); + rl.drawText(string, @as(i32, @intFromFloat(t.pos.x)), @as(i32, @intFromFloat(t.pos.y)), @as(i32, @intFromFloat(t.text.getTextHeight())), rl.Color.init( + t.text.colour.r, + t.text.colour.g, + t.text.colour.b, + t.text.colour.a, + )); + }, + .texture => continue, + } + } + + rl.clearBackground(.white); + _ = al.reset(.retain_capacity); + // rl.drawRectangle(0, 0, 200, 100, .blue); + + // rl.drawText("Congrats! You created your first window!", 190, 200, 20, .red); + + //---------------------------------------------------------------------------------- + } +}