const std = @import("std"); const rl = @import("raylib"); const rect_utils = @import("./rect-utils.zig"); const assert = std.debug.assert; const SourceLocation = std.builtin.SourceLocation; // TODO: Implement Id context (I.e. ID parenting) const UI = @This(); const max_stack_depth = 16; const TransformFrame = struct { offset: rl.Vector2, scale: rl.Vector2, }; hot_widget: ?Id = null, active_widget: ?Id = null, transform_stack: std.BoundedArray(TransformFrame, max_stack_depth), pub fn init() UI { var stack = std.BoundedArray(TransformFrame, max_stack_depth).init(0) catch unreachable; stack.appendAssumeCapacity(TransformFrame{ .offset = rl.Vector2{ .x = 0, .y = 0 }, .scale = rl.Vector2{ .x = 1, .y = 1 }, }); return UI{ .transform_stack = stack }; } pub fn isHot(self: *const UI, id: Id) bool { if (self.hot_widget) |hot_id| { return hot_id.eql(id); } return false; } pub fn isActive(self: *const UI, id: Id) bool { if (self.active_widget) |active_id| { return active_id.eql(id); } return false; } pub fn hashSrc(src: SourceLocation) u64 { var hash = std.hash.Fnv1a_64.init(); hash.update(src.file); hash.update(std.mem.asBytes(&src.line)); hash.update(std.mem.asBytes(&src.column)); return hash.value; } fn getTopFrame(self: *UI) *TransformFrame { assert(self.transform_stack.len >= 1); return &self.transform_stack.buffer[self.transform_stack.len-1]; } pub fn getMousePosition(self: *UI) rl.Vector2 { const frame = self.getTopFrame(); return rl.getMousePosition().subtract(frame.offset).divide(frame.scale); } pub fn getMouseDelta(self: *UI) rl.Vector2 { const frame = self.getTopFrame(); return rl.Vector2.multiply(rl.getMouseDelta(), frame.scale); } pub fn getMouseWheelMove(self: *UI) f32 { const frame = self.getTopFrame(); return rl.getMouseWheelMove() * frame.scale.y; } pub fn isMouseInside(self: *UI, rect: rl.Rectangle) bool { return rect_utils.isInsideVec2(rect, self.getMousePosition()); } pub fn transformScale(self: *UI, x: f32, y: f32) void { const frame = self.getTopFrame(); frame.scale.x *= x; frame.scale.y *= y; rl.gl.rlScalef(x, y, 1); } pub fn transformTranslate(self: *UI, x: f32, y: f32) void { const frame = self.getTopFrame(); frame.offset.x += x * frame.scale.x; frame.offset.y += y * frame.scale.y; rl.gl.rlTranslatef(x, y, 0); } pub fn pushTransform(self: *UI) void { rl.gl.rlPushMatrix(); self.transform_stack.appendAssumeCapacity(self.getTopFrame().*); } pub fn popTransform(self: *UI) void { assert(self.transform_stack.len >= 2); rl.gl.rlPopMatrix(); _ = self.transform_stack.pop(); } pub fn beginScissorMode(self: *UI, x: f32, y: f32, width: f32, height: f32) void { const frame = self.getTopFrame(); rl.beginScissorMode( @intFromFloat(x * frame.scale.x + frame.offset.x), @intFromFloat(y * frame.scale.y + frame.offset.y), @intFromFloat(width * frame.scale.x), @intFromFloat(height * frame.scale.y), ); } pub fn beginScissorModeRect(self: *UI, rect: rl.Rectangle) void { self.beginScissorMode(rect.x, rect.y, rect.width, rect.height); } pub fn endScissorMode(self: *UI) void { _ = self; rl.endScissorMode(); } pub const Id = struct { location: u64, extra: u32 = 0, pub fn init(comptime src: SourceLocation) Id { return Id{ .location = comptime hashSrc(src) }; } pub fn eql(a: Id, b: Id) bool { return a.location == b.location and a.extra == b.extra; } }; pub const Stack = struct { pub const Direction = enum { top_to_bottom, bottom_to_top, left_to_right }; unused_box: rl.Rectangle, dir: Direction, gap: f32 = 0, pub fn init(box: rl.Rectangle, dir: Direction) Stack { return Stack{ .unused_box = box, .dir = dir }; } pub fn next(self: *Stack, size: f32) rl.Rectangle { return switch (self.dir) { .top_to_bottom => { const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y, self.unused_box.width, size); self.unused_box.y += size; self.unused_box.y += self.gap; return next_box; }, .bottom_to_top => { const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y + self.unused_box.height - size, self.unused_box.width, size); self.unused_box.height -= size; self.unused_box.height -= self.gap; return next_box; }, .left_to_right => { const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y, size, self.unused_box.height); self.unused_box.x += size; self.unused_box.x += self.gap; return next_box; }, }; } }; pub const IdIterator = struct { id: Id, counter: u32, pub fn init(comptime src: SourceLocation) IdIterator { return IdIterator{ .id = Id.init(src), .counter = 0 }; } pub fn next(self: *IdIterator) Id { var id = self.id; id.extra = self.counter; self.counter += 1; return id; } };