game-2026-01-30/src/engine/imgui.zig
2026-01-30 23:28:18 +02:00

505 lines
10 KiB
Zig

const std = @import("std");
const Math = @import("./math.zig");
const build_options = @import("build_options");
pub const ig = @import("cimgui");
const Vec2 = Math.Vec2;
const Vec3 = Math.Vec3;
const Vec4 = Math.Vec4;
const sokol = @import("sokol");
const sapp = sokol.app;
const simgui = sokol.imgui;
const enabled = build_options.has_imgui;
var global_allocator: ?std.mem.Allocator = null;
pub const WindowOptions = struct {
name: [*c]const u8,
pos: ?Vec2 = null,
size: ?Vec2 = null,
collapsed: ?bool = null,
open: ?*bool = null
};
pub const SliderOptions = struct {
label: [*c]const u8,
value: *f32,
min: f32,
max: f32,
};
fn toImVec2(vec2: Vec2) ig.ImVec2 {
return ig.ImVec2{
.x = vec2.x,
.y = vec2.y,
};
}
inline fn structCast(T: type, value: anytype) T {
return @as(*T, @ptrFromInt(@intFromPtr(&value))).*;
}
pub fn setup(gpa: std.mem.Allocator, desc: simgui.Desc) void {
if (!enabled) {
return;
}
global_allocator = gpa;
simgui.setup(desc);
}
pub fn addFont(ttf_data: []const u8, font_size: f32) void {
if (!enabled) {
return;
}
var font_config: ig.ImFontConfig = .{};
font_config.FontDataOwnedByAtlas = false;
font_config.OversampleH = 2;
font_config.OversampleV = 2;
font_config.GlyphMaxAdvanceX = std.math.floatMax(f32);
font_config.RasterizerMultiply = 1.0;
font_config.RasterizerDensity = 1.0;
font_config.EllipsisChar = 0;
const io = ig.igGetIO();
_ = ig.ImFontAtlas_AddFontFromMemoryTTF(
io.*.Fonts,
@constCast(@ptrCast(ttf_data.ptr)),
@intCast(ttf_data.len),
font_size,
&font_config,
null
);
}
pub fn shutdown() void {
if (!enabled) {
return;
}
simgui.shutdown();
}
pub fn handleEvent(ev: sapp.Event) bool {
if (enabled) {
return simgui.handleEvent(ev);
} else {
return false;
}
}
pub fn newFrame(desc: simgui.FrameDesc) void {
if (!enabled) {
return;
}
simgui.newFrame(desc);
}
pub fn render() void {
if (!enabled) {
return;
}
simgui.render();
}
pub fn beginWindow(opts: WindowOptions) bool {
if (!enabled) {
return false;
}
if (opts.pos) |pos| {
ig.igSetNextWindowPos(toImVec2(pos), ig.ImGuiCond_Once);
}
if (opts.size) |size| {
ig.igSetNextWindowSize(toImVec2(size), ig.ImGuiCond_Once);
}
if (opts.collapsed) |collapsed| {
ig.igSetNextWindowCollapsed(collapsed, ig.ImGuiCond_Once);
}
ig.igSetNextWindowBgAlpha(1);
var open = ig.igBegin(opts.name, opts.open, ig.ImGuiWindowFlags_None);
if (opts.open) |opts_open| {
if (opts_open.* == false) {
open = false;
}
}
if (!open) {
endWindow();
}
return open;
}
pub fn endWindow() void {
if (!enabled) {
return;
}
ig.igEnd();
}
pub fn textFmt(comptime fmt: []const u8, args: anytype) void {
if (!enabled) {
return;
}
const gpa = global_allocator orelse return;
const formatted = std.fmt.allocPrintSentinel(gpa, fmt, args, 0) catch return;
defer gpa.free(formatted);
text(formatted);
}
pub fn text(text_z: [*c]const u8) void {
if (!enabled) {
return;
}
ig.igText("%s", text_z);
}
pub fn beginDisabled(disabled: bool) void {
if (!enabled) {
return;
}
ig.igBeginDisabled(disabled);
}
pub fn endDisabled() void {
if (!enabled) {
return;
}
ig.igEndDisabled();
}
pub fn button(label: [*c]const u8) bool {
if (!enabled) {
return false;
}
return ig.igButton(label);
}
pub fn slider(opts: SliderOptions) bool {
if (!enabled) {
return false;
}
return ig.igSliderFloat(opts.label, opts.value, opts.min, opts.max);
}
pub fn checkbox(label: [*c]const u8, value: *bool) bool {
if (!enabled) {
return false;
}
return ig.igCheckbox(label, value);
}
pub fn beginTabBar(id: [*c]const u8) bool {
if (!enabled) {
return false;
}
return ig.igBeginTabBar(id, ig.ImGuiTabBarFlags_None);
}
pub fn endTabBar() void {
if (!enabled) {
return;
}
ig.igEndTabBar();
}
pub fn beginTabItem(label: [*c]const u8) bool {
if (!enabled) {
return false;
}
return ig.igBeginTabItem(label, null, ig.ImGuiTabItemFlags_None);
}
pub fn endTabItem() void {
if (!enabled) {
return;
}
return ig.igEndTabItem();
}
pub fn beginGroup() void {
if (!enabled) {
return;
}
ig.igBeginGroup();
}
pub fn endGroup() void {
if (!enabled) {
return;
}
ig.igEndGroup();
}
pub fn sameLine() void {
if (!enabled) {
return;
}
ig.igSameLine();
}
pub fn beginTable(id: [*c]const u8, columns: u32, flags: ig.ImGuiTableFlags) bool {
if (!enabled) {
return false;
}
return ig.igBeginTable(id, @intCast(columns), flags);
}
pub fn endTable() void {
if (!enabled) {
return;
}
ig.igEndTable();
}
pub fn tableNextColumn() void {
if (!enabled) {
return;
}
_ = ig.igTableNextColumn();
}
pub fn tableNextRow() void {
if (!enabled) {
return;
}
_ = ig.igTableNextRow();
}
pub fn tableSetColumnIndex(index: usize) void {
if (!enabled) {
return;
}
_ = ig.igTableSetColumnIndex(@intCast(index));
}
pub fn tableSetupColumn(label: [*c]const u8, flags: ig.ImGuiTableColumnFlags) void {
if (!enabled) {
return;
}
ig.igTableSetupColumn(label, flags);
}
pub fn tableHeadersRow() void {
if (!enabled) {
return;
}
ig.igTableHeadersRow();
}
pub const ID = union(enum) {
string: []const u8,
int: i32
};
pub fn pushID(id: ID) void {
if (!enabled) {
return;
}
switch (id) {
.string => |str| ig.igPushIDStr(str.ptr, str.ptr + str.len),
.int => |int| ig.igPushIDInt(int)
}
}
pub fn popID() void {
if (!enabled) {
return;
}
ig.igPopID();
}
pub const TreeNodeFlags = packed struct {
selected: bool = false,
framed: bool = false,
allow_overlap: bool = false,
no_tree_pushOnOpen: bool = false,
no_auto_open_on_log: bool = false,
default_open: bool = false,
open_on_double_click: bool = false,
open_on_arrow: bool = false,
leaf: bool = false,
bullet: bool = false,
frame_padding: bool = false,
span_avail_width: bool = false,
span_full_width: bool = false,
span_label_width: bool = false,
span_all_columns: bool = false,
label_span_all_columns: bool = false,
nav_left_jumps_back_here: bool = false,
collapsing_header: bool = false,
fn toInt(self: TreeNodeFlags) u32 {
// TODO: Try using comptime to reduce this duplication.
// Would be great if `toInt()` could be replaced with just a @bitCast
//
// If the underlying C enum is exhaustive, maybe a bitcast could be performed?
// If the order of enums is correct
const flags = .{
.{ self.selected, ig.ImGuiTreeNodeFlags_Selected },
.{ self.framed, ig.ImGuiTreeNodeFlags_Framed },
.{ self.allow_overlap, ig.ImGuiTreeNodeFlags_AllowOverlap },
.{ self.no_tree_pushOnOpen, ig.ImGuiTreeNodeFlags_NoTreePushOnOpen },
.{ self.no_auto_open_on_log, ig.ImGuiTreeNodeFlags_NoAutoOpenOnLog },
.{ self.default_open, ig.ImGuiTreeNodeFlags_DefaultOpen },
.{ self.open_on_double_click, ig.ImGuiTreeNodeFlags_OpenOnDoubleClick },
.{ self.open_on_arrow, ig.ImGuiTreeNodeFlags_OpenOnArrow },
.{ self.leaf, ig.ImGuiTreeNodeFlags_Leaf },
.{ self.bullet, ig.ImGuiTreeNodeFlags_Bullet },
.{ self.frame_padding, ig.ImGuiTreeNodeFlags_FramePadding },
.{ self.span_avail_width, ig.ImGuiTreeNodeFlags_SpanAvailWidth },
.{ self.span_full_width, ig.ImGuiTreeNodeFlags_SpanFullWidth },
.{ self.span_label_width, ig.ImGuiTreeNodeFlags_SpanLabelWidth },
.{ self.span_all_columns, ig.ImGuiTreeNodeFlags_SpanAllColumns },
.{ self.label_span_all_columns, ig.ImGuiTreeNodeFlags_LabelSpanAllColumns },
.{ self.nav_left_jumps_back_here, ig.ImGuiTreeNodeFlags_NavLeftJumpsBackHere },
.{ self.collapsing_header, ig.ImGuiTreeNodeFlags_CollapsingHeader },
};
var sum: u32 = 0;
inline for (flags) |flag_pair| {
if (flag_pair[0]) {
sum += flag_pair[1];
}
}
return sum;
}
};
pub fn treeNode(label: [*c]const u8, flags: TreeNodeFlags) bool {
if (!enabled) {
return false;
}
return ig.igTreeNodeEx(label, @intCast(flags.toInt()));
}
pub fn treePop() void {
if (!enabled) {
return;
}
ig.igTreePop();
}
pub fn isItemClicked() bool {
if (!enabled) {
return false;
}
return ig.igIsItemClicked();
}
pub fn isItemToggledOpen() bool {
if (!enabled) {
return false;
}
return ig.igIsItemToggledOpen();
}
pub fn colorPicker4(label: [*c]const u8, color: *Vec4) bool {
if (!enabled) {
return false;
}
return ig.igColorPicker4(label, color.asArray().ptr, 0, null);
}
pub fn colorEdit4(label: [*c]const u8, color: *Vec4) bool {
if (!enabled) {
return false;
}
return ig.igColorEdit4(label, color.asArray().ptr, 0);
}
pub fn beginCombo(label: [*c]const u8, preview_value: [*c]const u8) bool {
if (!enabled) {
return false;
}
return ig.igBeginCombo(label, preview_value, 0);
}
pub fn endCombo() void {
if (!enabled) {
return;
}
ig.igEndCombo();
}
pub fn selectable(label: [*c]const u8, selected: bool) bool {
if (!enabled) {
return false;
}
return ig.igSelectableEx(label, selected, 0, .{ });
}
pub fn setItemDefaultFocus() void {
if (!enabled) {
return;
}
ig.igSetItemDefaultFocus();
}
pub fn combo(label: [*c]const u8, items: []const [*c]const u8, selected: *usize) void {
if (beginCombo(label, items[selected.*])) {
defer endCombo();
for (0.., items) |i, item| {
const is_selected = selected.* == i;
if (selectable(item, is_selected)) {
selected.* = i;
}
if (is_selected) {
setItemDefaultFocus();
}
}
}
}
pub fn separator() void {
if (!enabled) {
return;
}
ig.igSeparator();
}