const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; const log = std.log.scoped(.platform); const assert = std.debug.assert; const sokol = @import("sokol"); const sapp = sokol.app; const sg = sokol.gfx; const sglue = sokol.glue; const sgl = sokol.gl; const simgui = sokol.imgui; const Math = @import("./math.zig"); const Vec2 = Math.Vec2; const tracy = @import("tracy"); const ig = @import("cimgui"); const Color = @import("./color.zig"); fn PlatformType(App: type) type { _ = App; // autofix return struct { const Self = @This(); pass_action: sg.PassAction = .{}, cli_args: []const [:0]const u8, show_first_window: bool = true, fn sokolInit(user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); sg.setup(.{ .environment = sglue.environment(), .logger = .{ .func = sokolLogCallback }, }); simgui.setup(.{ .logger = .{ .func = sokolLogCallback }, }); if (@hasDecl(ig, "ImGuiConfigFlags_DockingEnable")) { ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; } ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_ViewportsEnable; self.pass_action.colors[0] = .{ .load_action = .CLEAR, .clear_value = toSokolColor(.black) }; } fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); tracy.frameMark(); simgui.newFrame(.{ .width = sapp.width(), .height = sapp.height(), .delta_time = sapp.frameDuration(), .dpi_scale = sapp.dpiScale(), }); //=== UI CODE STARTS HERE { const viewport = ig.igGetMainViewport(); ig.igSetNextWindowPos(viewport[0].WorkPos, ig.ImGuiCond_Always); ig.igSetNextWindowSize(viewport[0].WorkSize, ig.ImGuiCond_Always); ig.igSetNextWindowViewport(viewport[0].ID); const window_flags = ig.ImGuiWindowFlags_NoDocking | ig.ImGuiWindowFlags_NoTitleBar | ig.ImGuiWindowFlags_NoCollapse | ig.ImGuiWindowFlags_NoResize | ig.ImGuiWindowFlags_NoMove | ig.ImGuiWindowFlags_NoBringToFrontOnFocus | ig.ImGuiWindowFlags_NoNavFocus; ig.igPushStyleVar(ig.ImGuiStyleVar_WindowRounding, 0.0); ig.igPushStyleVar(ig.ImGuiStyleVar_WindowBorderSize, 0.0); ig.igPushStyleVarImVec2(ig.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 }); _ = ig.igBegin("DockSpace", null, window_flags); ig.igPopStyleVarEx(3); const dockspace_id = ig.igGetID("MyDockSpace"); _ = ig.igDockSpaceEx(dockspace_id, ig.ImVec2{ .x = 0, .y = 0 }, ig.ImGuiDockNodeFlags_PassthruCentralNode, null); ig.igSetNextWindowPos(.{ .x = 10, .y = 30 }, ig.ImGuiCond_Once); ig.igSetNextWindowSize(.{ .x = 400, .y = 100 }, ig.ImGuiCond_Once); if (ig.igBegin("Hello Dear ImGui!", &self.show_first_window, ig.ImGuiWindowFlags_None)) { _ = ig.igColorEdit3("Background", &self.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None); _ = ig.igText("Dear ImGui Version: %s", ig.IMGUI_VERSION); } ig.igEnd(); ig.igEnd(); } // TODO: // if (ig.igBeginMainMenuBar()) { // ig.igEndMainMenuBar(); // } //=== UI CODE ENDS HERE // var frame = Platform.Frame{ // .platform = &self.exposed, // .input = .{}, // .output = .{}, // }; // if (self.frame(&frame)) { // // ? // } else |e| { // log.err("frame() failed: {}", .{e}); // if (@errorReturnTrace()) |trace| { // std.debug.dumpErrorReturnTrace(trace); // } // self.quit(); // } // var pass_action: sg.PassAction = .{}; // pass_action.colors[0] = .{ // .load_action = .CLEAR, // .clear_value = toSokolColor(.black) // }; sg.beginPass(.{ .action = self.pass_action, .swapchain = sglue.swapchain() }); simgui.render(); sg.endPass(); sg.commit(); } fn sokolEvent(ev: [*c]const sapp.Event, user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); _ = self; // autofix // forward input events to sokol-imgui _ = simgui.handleEvent(ev.*); } fn sokolCleanup(user_data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(user_data)); _ = self; // autofix // self.deinit(&self.exposed); simgui.shutdown(); sg.shutdown(); } }; } pub const RunOptions = struct { init: std.process.Init, window_title: [*c]const u8 = null, width: i32 = 800, height: i32 = 600, }; pub fn run(App: type, opts: RunOptions) void { const arena = opts.init.arena.allocator(); const gpa = opts.init.gpa; _ = gpa; // autofix const Platform = PlatformType(App); const plt = arena.create(Platform) catch @panic("OOM"); plt.* = Platform{ .cli_args = opts.init.minimal.args.toSlice(arena) catch @panic("OOM") }; // var init = Platform.Init{ // .cli_args = self.cli_args // }; // if (self.init(&self.exposed, &init)) |maybe_error_code| { // if (maybe_error_code) |error_code| { // std.process.exit(error_code); // } // } else |e| { // log.err("init() failed: {}", .{e}); // if (@errorReturnTrace()) |trace| { // std.debug.dumpErrorReturnTrace(trace); // } // std.process.exit(1); // } if (builtin.os.tag == .linux) { var sa: std.posix.Sigaction = .{ .handler = .{ .handler = posixSignalHandler }, .mask = std.posix.sigemptyset(), .flags = std.posix.SA.RESTART, }; std.posix.sigaction(std.posix.SIG.INT, &sa, null); } var icon: sapp.IconDesc = .{}; icon.sokol_default = true; // TODO: Don't hard code icon path, allow changing through options // var icon_data = try STBImage.load(@embedFile("../assets/icon.png")); // defer icon_data.deinit(); // TODO: // icon.images[0] = .{ // .width = @intCast(icon_data.width), // .height = @intCast(icon_data.height), // .pixels = .{ // .ptr = icon_data.rgba8_pixels, // .size = icon_data.width * icon_data.height * 4 // } // }; tracy.setThreadName("Main"); sapp.run(.{ .init_userdata_cb = Platform.sokolInit, .frame_userdata_cb = Platform.sokolFrame, .cleanup_userdata_cb = Platform.sokolCleanup, .event_userdata_cb = Platform.sokolEvent, .user_data = plt, .window_title = opts.window_title, .width = opts.width, .height = opts.height, .icon = icon, .logger = .{ .func = sokolLogCallback }, }); } fn posixSignalHandler(sig: std.posix.SIG) callconv(.c) void { _ = sig; sapp.requestQuit(); } fn toSokolColor(color: Color) sokol.gfx.Color { // TODO: Assert and do cast return sokol.gfx.Color{ .r = color.r, .g = color.g, .b = color.b, .a = color.a, }; } fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void { if (log_level == 0) { log.err(format, args); } else if (log_level == 1) { log.err(format, args); } else if (log_level == 2) { log.warn(format, args); } else { log.info(format, args); } } fn cStrToZig(c_str: [*c]const u8) [:0]const u8 { return std.mem.span(c_str); } fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void { _ = user_data; if (filename != null) { sokolLogFmt( log_level, "[{s}][id:{}] {s}:{}: {s}", .{ cStrToZig(tag orelse "-"), log_item, std.fs.path.basename(cStrToZig(filename orelse "-")), line_nr, cStrToZig(message orelse "") } ); } else { sokolLogFmt( log_level, "[{s}][id:{}] {s}", .{ cStrToZig(tag orelse "-"), log_item, cStrToZig(message orelse "") } ); } }