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(); }