201 lines
5.3 KiB
Zig
201 lines
5.3 KiB
Zig
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;
|
|
}
|
|
};
|