From f4a0d14b63401dc0cade34f695e6aeeeb99ade40 Mon Sep 17 00:00:00 2001 From: sirlilpanda Date: Wed, 14 Jan 2026 00:36:32 +1300 Subject: [PATCH] start of the lib --- src/main.zig | 378 +++++++++++++++++++++++ src/root.zig | 824 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1202 insertions(+) create mode 100644 src/main.zig create mode 100644 src/root.zig diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..9edd382 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,378 @@ +const std = @import("std"); +const rl = @import("rl"); +const bmb = @import("pandas_ui_lib"); + +fn getTextWidth(string: []const u8, font: rl.Font) i32 { + 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(bmb.Real, @intCast(rl.measureText(s, @as(i32, @intCast(font.baseSize))))); +} + +fn getTextheight(string: []const u8, font: rl.Font) i32 { + _ = string; + // std.debug.print("font.baseSize : {}\n", .{font.baseSize}); + return @as(bmb.Real, @intCast(font.baseSize)); +} + +const TextType = bmb.TextType(rl.Font, getTextWidth, getTextheight); +const TextureType = bmb.TextureType(rl.Texture2D); + +const UI = bmb.Bamboo(TextType, TextureType); + +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 button() UI.Node { + return UI.Element(.{ + .name = "button", + .rect = .{ + .h = 20, + .w = 60, + }, + .style = .{ + .background_colour = .{ + .g = 255, + }, + }, + }); +} + +pub fn main() anyerror!void { + // Initialization + //-------------------------------------------------------------------------------------- + const screenWidth = 1920; + const screenHeight = 1080; + + 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("bmb version :{s}\n", .{bmb.VERSON}); + + const child = &[_]UI.Node{ + sideBar(), + button(), + UI.Element(.{ + .style = .{ + .background_colour = .{ + .b = 120, + }, + .padding = .{ + .bottom = 10, + .left = 10, + .right = 10, + .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(), + }; + + 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); + + const sized = try UI.resolveSizing(al.allocator(), root); + // UI.printTree(sized); + const commands = try UI.getRenderCommands(sized, al.allocator()); + + // 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(); + + for (commands.items) |command| { + // std.debug.print("command : {any}\n", .{command}); + switch (command) { + .rect => |r| { + if (r.rounding) |rounding| { + rl.drawRectangleRounded( + rl.Rectangle.init( + @as(f32, @floatFromInt(r.pos.x)), + @as(f32, @floatFromInt(r.pos.y)), + @as(f32, @floatFromInt(r.rect.w)), + @as(f32, @floatFromInt(r.rect.h)), + ), + @as(f32, @floatFromInt(rounding)) / 100.0, + 10, + rl.Color.init( + r.colour.r, + r.colour.g, + r.colour.b, + r.colour.a, + ), + ); + } else { + rl.drawRectangle(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, t.pos.x, t.pos.y, 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); + + // rl.drawRectangle(0, 0, 200, 100, .blue); + + // rl.drawText("Congrats! You created your first window!", 190, 200, 20, .red); + + //---------------------------------------------------------------------------------- + } +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..c2e2a75 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,824 @@ +//! By convention, root.zig is the root source file when making a library. +const std = @import("std"); + +// base drawing functions +// text, +// rect, +// texture, + +// sissor +pub const VERSON = "0.0.1"; +pub const Real = i32; +const Spaceing = 4; + +pub fn TextType( + comptime FontType: type, + comptime getTextWidthFunc: fn (string: []const u8, FontType) Real, + comptime getTextHeightFunc: fn (string: []const u8, FontType) Real, +) type { + return struct { + const Optional = struct { + font: FontType, + }; + const Self = @This(); + string: []const u8, + colour: Colour, + background: Colour, + font: FontType, + + pub fn init(string: []const u8, font: FontType, colour: Colour) Self { + return Self{ + .string = string, + .font = font, + .colour = colour, + .background = .{ + .r = 0, + .b = 0, + .g = 0, + .a = 0, + }, + }; + } + + pub fn setBackground(self: Self, background: Colour) Self { + var s = self; + s.background = background; + return s; + } + + pub fn getTextWidth(self: Self) Real { + return getTextWidthFunc(self.string, self.font); + } + + pub fn getTextHeight(self: Self) Real { + return getTextHeightFunc(self.string, self.font); + } + }; +} + +pub fn TextureType(comptime TextureData: type) type { + return struct { + texture: TextureData, + rect: Rect, + }; +} + +pub const Rect = struct { + w: Real = 0, + h: Real = 0, +}; + +pub const Pos = struct { + x: Real = 0, + y: Real = 0, +}; + +const Sizing = enum { + fixed, + grow, + min, + max, + fit, +}; + +pub const Padding = struct { + top: Real = 0, + bottom: Real = 0, + left: Real = 0, + right: Real = 0, +}; + +pub const Layout = enum { + right_to_left, + top_to_bottom, +}; + +pub const Style = struct { + size_x: Sizing = .fit, + size_y: Sizing = .fit, + + background_colour: Colour = .{}, + rounded: ?Real = null, + + // require re-draw + padding: Padding = .{}, + child_gap: Real = 0, + layout: Layout = .top_to_bottom, + + // not using formatting so i can print it nicely + pub fn printStyle(style: Style, level: usize) void { + for (0..level * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("style : .{{\n", .{}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("size_x : {any},\n", .{style.size_x}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("size_y : {any},\n", .{style.size_y}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("background_colour : {any},\n", .{style.background_colour}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("rounded : {any},\n", .{style.rounded}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("padding : {any},\n", .{style.padding}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("child_gap : {any},\n", .{style.child_gap}); + for (0..(level + 1) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("layout : {any},\n", .{style.layout}); + for (0..(level) * Spaceing) |_| std.debug.print(" ", .{}); + std.debug.print("}},\n", .{}); + } +}; + +pub const Colour = struct { + r: u8 = 0, + g: u8 = 0, + b: u8 = 0, + a: u8 = 255, +}; + +pub const State = enum { + None, + hovered, + clicked, +}; + +pub fn Bamboo( + comptime text_type: type, + comptime texture_type: type, + // comptime renderTextFunc: fn (text_type, Pos) void, + // comptime renderRectFunc: fn (Rect, Pos, Colour, rounding: ?Real) void, + // comptime renderTextureFunc: fn (texture_type, Pos) void, +) type { + return struct { + const Self = @This(); + + pub const Text = text_type; + pub const Texture = texture_type; + + const NodeTypes = enum { + element, + text, + texture, + }; + pub const Node = union(NodeTypes) { + element: Ele, + text: Text, + texture: Texture, + }; + + pub const Ele = struct { + const Self = @This(); + name: []const u8 = "", + z_index: usize = 0, + pos: Pos = .{}, + rect: Rect = .{}, + style: Style = .{}, + children: []const Node = &[_]Node{}, + }; + + const RenderCommandType = enum { + rect, + text, + texture, + }; + pub const RenderCommand = union(RenderCommandType) { + rect: struct { + rect: Rect, + pos: Pos, + colour: Colour, + rounding: ?Real, + z_index: usize = 0, + }, + text: struct { + text: Text, + pos: Pos, + z_index: usize = 0, + }, + texture: struct { + texture: Texture, + pos: Pos, + z_index: usize = 0, + }, + }; + + // /// highly suggested that you use an area for the alloc + // pub fn init(alloc: std.mem.Allocator) Self { + // return Self{ + // .alloc = alloc, + // }; + // } + + pub inline fn Element(ele: Ele) Node { + return Node{ + .element = ele, + }; + } + + pub inline fn Txt(text: Text) Node { + return Element( + .{ + .style = .{ + .background_colour = text.background, + }, + .children = &[_]Node{.{ + .text = text, + }}, + }, + ); + } + + pub fn closeElement(node: *Node, parent: *Node) void { + switch (node.*) { + .element => { + // layout + const child_gap: Real = parent.element.style.child_gap; + + switch (parent.element.style.layout) { + .right_to_left => parent.element.rect.w += child_gap, + .top_to_bottom => parent.element.rect.h += child_gap, + } + + const padding = node.element.style.padding; + + node.element.rect.w += padding.left + padding.right; + node.element.rect.h += padding.top + padding.bottom; + + switch (parent.element.style.layout) { + .right_to_left => { + parent.element.rect.h = @max(node.element.rect.h, parent.element.rect.h); + parent.element.rect.w += node.element.rect.w; + }, + + .top_to_bottom => { + parent.element.rect.h += node.element.rect.h; + parent.element.rect.w = @max(node.element.rect.w, parent.element.rect.w); + }, + } + }, + .text => { + parent.element.rect.h += node.text.getTextHeight(); + std.debug.print("parent.element.rect.h : {}\n", .{parent.element.rect.h}); + parent.element.rect.w += node.text.getTextWidth(); + std.debug.print("parent.element.rect.w : {}\n", .{parent.element.rect.w}); + }, + .texture => return, + } + } + + fn deepCloneHelper(node: Node, node_location: *Node, alloc: std.mem.Allocator) !void { + // std.debug.print("nodeloc : {}\n", .{node_location}); + node_location.* = node; + // std.debug.print("nodeloc : {}\n", .{node_location}); + switch (node) { + .element => |ele| { + // BASE CASE + // node_location.element.name = "cloned"; + if (ele.children.len == 0) return; + const children = try alloc.alloc(Node, ele.children.len); + // std.debug.print("children Alloc {x}\n", .{@intFromPtr(children.ptr)}); + for (ele.children, 0..) |child, i| { + try deepCloneHelper(child, &children[i], alloc); + // std.debug.print("base node_loc {x}\n", .{@intFromPtr(node_location.element.children.ptr)}); + node_location.*.element.children = children; + // std.debug.print("up[date node_loc {x}\n", .{@intFromPtr(node_location.element.children.ptr)}); + } + }, + else => return, + } + } + + // we deep clone the root node as this make it easier for the temp node + fn deepClone(node: Node, alloc: std.mem.Allocator) !Node { + var new_node = node; + switch (new_node) { + .element => |ele| { + // BASE CASE + if (ele.children.len == 0) return new_node; + const children = try alloc.alloc(Node, ele.children.len); + for (ele.children, 0..) |child, i| { + try deepCloneHelper(child, &children[i], alloc); + new_node.element.children = children; + } + }, + else => return new_node, + } + + return new_node; + } + + fn printTreeHelper(node: Node, level: usize) void { + switch (node) { + .element => { + for (0..level * 4) |_| std.debug.print(" ", .{}); + std.debug.print("{s} : Ele{{\n", .{node.element.name}); + for (0..(level + 1) * 4) |_| std.debug.print(" ", .{}); + std.debug.print("pos : {any},\n", .{node.element.pos}); + for (0..(level + 1) * 4) |_| std.debug.print(" ", .{}); + std.debug.print("rect : {any},\n", .{node.element.rect}); + node.element.style.printStyle(level + 1); + for (0..(level + 1) * 4) |_| std.debug.print(" ", .{}); + std.debug.print("children : {{\n", .{}); + for (node.element.children, 0..) |_, i| { + printTreeHelper(node.element.children[i], (level + 2)); + } + for (0..(level + 1) * 4) |_| std.debug.print(" ", .{}); + std.debug.print("}},\n", .{}); + for (0..(level) * 4) |_| std.debug.print(" ", .{}); + std.debug.print("}},\n", .{}); + }, + .text => { + for (0..level * 4) |_| std.debug.print(" ", .{}); + std.debug.print("text : {s}.\n", .{node.text.string}); + return; + }, + .texture => return, + } + } + + pub fn printTree(node: Node) void { + printTreeHelper(node, 0); + } + + pub fn getRenderCommandsHelper(node: Node, alloc: std.mem.Allocator, command_list: *std.ArrayList(RenderCommand)) !void { + switch (node) { + .element => { + // std.debug.print("parent name : {s}\n", .{parent.element.name}); + try command_list.append(alloc, .{ + .rect = .{ + .colour = node.element.style.background_colour, + .pos = node.element.pos, + .rect = node.element.rect, + .rounding = node.element.style.rounded, + .z_index = node.element.z_index, + }, + }); + + for (node.element.children, 0..) |_, i| { + switch (node.element.children[i]) { + .element => try getRenderCommandsHelper(node.element.children[i], alloc, command_list), + .text => |text| try command_list.append(alloc, .{ .text = .{ + .pos = node.element.pos, + .text = text, + .z_index = node.element.z_index, + } }), + .texture => |texture| try command_list.append(alloc, .{ .texture = .{ + .pos = node.element.pos, + .texture = texture, + .z_index = node.element.z_index, + } }), + } + } + }, + else => return, // the commands get created above, since the text and texture need a pos + // but they dont have the pos one the parent has it + } + } + + pub fn getRenderCommands(node: Node, alloc: std.mem.Allocator) !std.ArrayList(RenderCommand) { + var render_commands: std.ArrayList(RenderCommand) = .empty; + try getRenderCommandsHelper(node, alloc, &render_commands); + return render_commands; + } + + fn computeSizes(node: *Node, parent: *Node) void { + switch (node.*) { + .element => { + // std.debug.print("parent name : {s}\n", .{parent.element.name}); + + for (node.element.children, 0..) |_, i| { + computeSizes(@constCast(&node.element.children[i]), node); + } + + // cheaters way to beat the fence post problem + // TODO fix this + switch (node.element.style.layout) { + .right_to_left => node.element.rect.w -= node.element.style.child_gap, + .top_to_bottom => node.element.rect.h -= node.element.style.child_gap, + } + }, + else => {}, + } + closeElement(node, parent); + } + + fn filterGrowableChildren(index: *usize, node: Node) ?*Ele { + switch (node) { + .element => { + if (node.element.children.len == 0) return null; + + var i = index.*; + while (i < node.element.children.len - 1) : (i += 1) { + switch (node.element.children[i]) { + .element => |e| { + if (e.style.size_x == .grow or e.style.size_y == .grow) { + index.* = i + 1; + return @constCast(&node.element.children[i].element); + } + }, + else => continue, + } + } + }, + else => return null, + } + return null; + } + + fn computeRemainingHeightAndWidth(node: Node) struct { Real, Real } { + // growable code + std.debug.print("node.element.rect.w : {d}\n", .{node.element.rect.w}); + var remaining_width = node.element.rect.w; + var remaining_height = node.element.rect.h; + std.debug.print("remaining_width : {d}\n", .{remaining_width}); + std.debug.print("remaining_hieght : {d}\n", .{remaining_height}); + + remaining_width -|= node.element.style.padding.left + node.element.style.padding.right; + remaining_height -|= node.element.style.padding.top + node.element.style.padding.bottom; + + if (node.element.children.len == 0) return .{ + 0, + 0, + }; + + std.debug.print("remaining_width : {d}\n", .{remaining_width}); + std.debug.print("remaining_hieght : {d}\n", .{remaining_height}); + + // are there too many indentaions here maybe + for (node.element.children) |child| { + std.debug.print("child : {}\n", .{child}); + switch (child) { + .element => { + remaining_height -= child.element.rect.h; + remaining_width -= child.element.rect.w; + std.debug.print("remaining_width : {d}\n", .{remaining_width}); + std.debug.print("remaining_hieght : {d}\n", .{remaining_height}); + }, + .texture => { + switch (node.element.style.layout) { + .top_to_bottom => { + remaining_height -|= child.texture.rect.h; + remaining_width = @max(remaining_width, child.texture.rect.w); + }, + .right_to_left => { + remaining_width -|= child.texture.rect.w; + remaining_height = @max(remaining_height, child.texture.rect.h); + }, + } + }, + else => {}, + } + } + std.debug.print("remaining_width : {d}\n", .{remaining_width}); + std.debug.print("remaining_hieght : {d}\n", .{remaining_height}); + remaining_width -|= (@as(Real, @intCast(node.element.children.len -| 1)) * node.element.style.child_gap); + remaining_height -|= (@as(Real, @intCast(node.element.children.len -| 1)) * node.element.style.child_gap); + std.debug.print("remaining_width : {d}\n", .{remaining_width}); + std.debug.print("remaining_hieght : {d}\n", .{remaining_height}); + + return .{ + remaining_height, + remaining_width, + }; + } + + pub fn getHeight(element: *const Ele) Real { + return element.rect.h; + } + + pub fn getWidth(element: *const Ele) Real { + return element.rect.w; + } + + pub fn addHeight(element: *Ele, height: Real) void { + element.rect.h += height; + } + + pub fn addWidth(element: *Ele, width: Real) void { + element.rect.w += width; + } + + pub fn getSizingY(element: *const Ele) Sizing { + return element.style.size_y; + } + + pub fn getSizingX(element: *const Ele) Sizing { + return element.style.size_x; + } + + fn growChildIndependedElements( + node: Node, + axis_x: bool, + ) void { + const padding_x = node.element.style.padding.left + node.element.style.padding.right; + const padding_y = node.element.style.padding.left + node.element.style.padding.right; + + for (node.element.children, 0..) |child, i| { + switch (child) { + .element => |e| { + if (e.style.size_x == .grow and axis_x) { + @constCast(&node.element.children[i]).element.rect.w = node.element.rect.w - padding_x; + } + if (e.style.size_y == .grow and !axis_x) { + @constCast(&node.element.children[i]).element.rect.h = node.element.rect.h - padding_y; + } + }, + else => {}, + } + } + } + + fn growChildDependedElements( + node: Node, + remaining_disance: Real, + total_growable: usize, + getAxis: *const fn (*const Ele) Real, + addToAxis: *const fn (*Ele, Real) void, + getSizing: *const fn (*const Ele) Sizing, + ) void { + var remaining = remaining_disance; + + while (remaining > 0) { + var find_next_smallest_index: usize = 0; + var smallest = filterGrowableChildren(&find_next_smallest_index, node) orelse return; + var second_smallest = &Ele{ .rect = .{ + .h = std.math.maxInt(Real), + .w = std.math.maxInt(Real), + } }; + var width_to_add = remaining; + var next_smallest_index: usize = 0; + while (filterGrowableChildren(&next_smallest_index, node)) |e| { + if (getAxis(e) < getAxis(smallest)) { + second_smallest = smallest; + smallest = e; + } + + if (getAxis(e) > getAxis(smallest)) { + second_smallest = if (getAxis(second_smallest) < getAxis(e)) second_smallest else e; + width_to_add = getAxis(second_smallest) - getAxis(smallest); + } + } + width_to_add = @min(width_to_add, @divTrunc(remaining, @as(Real, @intCast(total_growable)))); + if (width_to_add == 0) return; + var grow_em: usize = 0; + std.debug.print("smallest ; {}\n", .{smallest.rect}); + while (filterGrowableChildren(&grow_em, node)) |growable| { + if (getAxis(growable) == getAxis(smallest)) { + std.debug.print("{s} before growable : {} + width_to_add {}\n", .{ growable.name, growable.rect, width_to_add }); + if (getSizing(growable) == .grow) + addToAxis(growable, width_to_add); + std.debug.print("after growable : {}\n", .{growable.rect}); + remaining -|= width_to_add; + } + } + } + } + + pub fn growChildern(node: *Node) void { + // check that there are growable children + var total_growable_index: usize = 0; + var total_growable: usize = 0; + while (filterGrowableChildren(&total_growable_index, node.*)) |_| : (total_growable += 1) continue; + + if (total_growable == 0) return; + + const remaining_height, const remaining_width = computeRemainingHeightAndWidth(node.*); + std.debug.print("remaining_height remaining_width : {} {}\n", .{ remaining_height, remaining_width }); + + if (node.element.style.layout == .top_to_bottom) { + growChildIndependedElements(node.*, true); + if (remaining_height > 0) growChildDependedElements( + node.*, + remaining_height, + total_growable, + &getHeight, + &addHeight, + getSizingY, + ); + } + + if (node.element.style.layout == .right_to_left) { + growChildIndependedElements(node.*, false); + + if (remaining_width > 0) growChildDependedElements( + node.*, + remaining_width, + total_growable, + &getWidth, + &addWidth, + getSizingX, + ); + } + } + + pub fn computeGrowElements(node: *Node) void { + switch (node.*) { + .element => { + growChildern(node); + for (node.element.children, 0..) |_, i| { + computeGrowElements(@constCast(&node.element.children[i])); + } + }, + else => return, + } + } + + fn computeChildernsPostions(ele: *Ele) void { + const layout_style = ele.style.layout; + var children = @constCast(ele.children); + + switch (layout_style) { + .right_to_left => { + var off_set_left: Real = 0; + for (ele.children, 0..) |_, i| { + const child_gap: Real = ele.style.child_gap; + switch (ele.children[i]) { + .element => { + // off set it by the parent + children[i].element.pos.x += ele.pos.x + ele.style.padding.left + off_set_left; + children[i].element.pos.y += ele.pos.y + ele.style.padding.top; + off_set_left += children[i].element.rect.w + child_gap; + // add + }, + else => continue, + } + } + }, + .top_to_bottom => { + var offset_top: Real = 0; + for (ele.children, 0..) |_, i| { + const child_gap: Real = ele.style.child_gap; + switch (ele.children[i]) { + .element => { + // off set it by the parent + children[i].element.pos.x += ele.pos.x + ele.style.padding.left; + children[i].element.pos.y += ele.pos.y + ele.style.padding.top + offset_top; + offset_top += children[i].element.rect.h + child_gap; + // add + }, + else => continue, + } + } + }, + } + } + + pub fn computePostionsHelper(node: *Node, index: usize) void { + switch (node.*) { + .element => { + computeChildernsPostions(&node.element); + node.element.z_index = index; + for (node.element.children, 0..) |_, i| { + computePostionsHelper(@constCast(&node.element.children[i]), index + 1); + } + }, + else => return, + } + } + + pub fn computePostions(node: *Node) void { + computePostionsHelper(node, 0); + } + + pub fn resolveSizing(alloc: std.mem.Allocator, root: Node) !Node { + // var resolved_root: Node = undefined; + // var current_node: Node = root; + // var pass_queue: std.ArrayList([]const u8) = .empty; + + const node_1 = root; + var new_tree: Node = try deepClone(node_1, alloc); + + // printTree(node_1); + // printTree(new_tree); + + var base_node: Node = Node{ .element = Ele{ + .children = &[_]Node{ + node_1, + }, + } }; + computeSizes(&new_tree, &base_node); + + // printTree(new_tree); + + computeGrowElements(&new_tree); + computePostions(&new_tree); + // printTree(new_tree); + + return new_tree; + } + + // pub fn freeTree(alloc: std.mem.Allocator, root: Node) void {} + + // pub fn render(self: Self, root: Node) void { + // return null; + // } + }; +} + +fn callback(dat: anytype) void { + _ = dat; + return null; +} + +pub fn sideBar() UI.Node { + return UI.Element(.{ + .name = "sidebar", + }); +} + +pub fn button() UI.Node { + return UI.Element(.{ + .name = "button", + }); +} + +fn func(string: []const u8, font: void) Real { + _ = font; + return @as(Real, @intCast(string.len)); +} + +const UI = Bamboo(TextType(void, func, func), void); + +test "temp" { + const child = &[_]UI.Node{ + sideBar(), + button(), + UI.Node{ + .text = UI.Text.init( + "hello world", + void{}, + .{}, + ), + }, + UI.Element(.{ + .name = "child", + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch1", + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch11", + .rect = .{ + .h = 400, + .w = 400, + }, + }), + UI.Element(.{ + .name = "branch12", + .rect = .{ + .h = 400, + .w = 400, + }, + }), + }, + }), + UI.Element(.{ + .name = "branch2", + .children = &[_]UI.Node{ + UI.Element(.{ + .name = "branch21", + .rect = .{ + .h = 300, + .w = 300, + }, + }), + UI.Element(.{ + .name = "branch22", + .rect = .{ + .h = 300, + .w = 300, + }, + }), + }, + }), + }, + }), + button(), + }; + + const root = UI.Element(.{ + .name = "root", + .pos = Pos{ + .x = 0, + .y = 0, + }, + // .on_hover = callback(), + // .on_click = callback(), + .style = .{ + .background_colour = .{}, + .layout = .right_to_left, + // .size = .fit(), + // .size_x = .fit(), + // .size_y = .fixed(), + }, + .children = child, + }); + + // const arena = [1024]u8{0}; + + var al = std.heap.ArenaAllocator.init(std.heap.smp_allocator); + + const sized = try UI.resolveSizing(al.allocator(), root); + const commands = try UI.getRenderCommands(sized, al.allocator()); + for (commands.items) |value| { + std.debug.print("command : {any}\n", .{value}); + } +}