505 lines
10 KiB
Zig
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();
|
|
}
|