diff --git a/3rd-party/cimgui/build.zig b/3rd-party/cimgui/build.zig new file mode 100644 index 0000000..76574c8 --- /dev/null +++ b/3rd-party/cimgui/build.zig @@ -0,0 +1,66 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const dep_cimgui = b.dependency("cimgui", .{}); + const dep_imgui = b.dependency("imgui", .{}); + + // create file tree for cimgui and imgui + const wf = b.addNamedWriteFiles("cimgui"); + _ = wf.addCopyDirectory(dep_cimgui.path(""), "", .{}); + _ = wf.addCopyDirectory(dep_imgui.path(""), "imgui", .{}); + const root = wf.getDirectory(); + + // build cimgui as C/C++ library + const lib_cimgui = b.addStaticLibrary(.{ + .name = "cimgui_clib", + .target = target, + .optimize = optimize, + .link_libc = true, + }); + lib_cimgui.linkLibCpp(); + lib_cimgui.addCSourceFiles(.{ + .root = root, + .files = &.{ + b.pathJoin(&.{"cimgui.cpp"}), + b.pathJoin(&.{ "imgui", "imgui.cpp" }), + b.pathJoin(&.{ "imgui", "imgui_widgets.cpp" }), + b.pathJoin(&.{ "imgui", "imgui_draw.cpp" }), + b.pathJoin(&.{ "imgui", "imgui_tables.cpp" }), + b.pathJoin(&.{ "imgui", "imgui_demo.cpp" }), + }, + }); + lib_cimgui.addIncludePath(root); + + // make cimgui available as artifact, this then allows to inject + // the Emscripten include path in another build.zig + b.installArtifact(lib_cimgui); + + // lib compilation depends on file tree + lib_cimgui.step.dependOn(&wf.step); + + // translate-c the cimgui.h file + // NOTE: always run this with the host target, that way we don't need to inject + // the Emscripten SDK include path into the translate-C step when building for WASM + const cimgui_h = dep_cimgui.path("cimgui.h"); + const translateC = b.addTranslateC(.{ + .root_source_file = cimgui_h, + .target = b.host, + .optimize = optimize, + }); + translateC.defineCMacroRaw("CIMGUI_DEFINE_ENUMS_AND_STRUCTS=\"\""); + const entrypoint = translateC.getOutput(); + + // build cimgui as a module with the header file as the entrypoint + const mod_cimgui = b.addModule("cimgui", .{ + .root_source_file = entrypoint, + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }); + mod_cimgui.linkLibrary(lib_cimgui); +} diff --git a/3rd-party/cimgui/build.zig.zon b/3rd-party/cimgui/build.zig.zon new file mode 100644 index 0000000..0e6b930 --- /dev/null +++ b/3rd-party/cimgui/build.zig.zon @@ -0,0 +1,18 @@ +.{ + .name = "cimgui", + .version = "0.0.0", + + .dependencies = .{ + .cimgui = .{ + .url = "git+https://github.com/cimgui/cimgui.git#1.90.7", + .hash = "1220d925a8374fbc3a21a5b46025fb867672d85b4099d2151bed607618e08b0cd71c", + }, + .imgui = .{ + .url = "git+https://github.com/ocornut/imgui.git#v1.90.7", + .hash = "122072b125179c7cbdbbee8fa81d22a1050a950ad61cfeefee8dc0dca5423b5d05b9", + }, + }, + .paths = .{ + "", + }, +} diff --git a/build.zig b/build.zig index 8992841..bc7da3d 100644 --- a/build.zig +++ b/build.zig @@ -1,30 +1,90 @@ const std = @import("std"); -const capy = @import("capy"); +const Build = std.Build; +const OptimizeMode = std.builtin.OptimizeMode; +const ResolvedTarget = Build.ResolvedTarget; +const Dependency = Build.Dependency; +const sokol = @import("sokol"); -pub fn build(b: *std.Build) !void { +pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); - const mode = b.standardOptimizeOption(.{}); + const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "doggo", - .root_source_file = .{ .path = "src/main.zig" }, + // note that the sokol dependency is built with `.with_imgui_sokol = true` + const dep_sokol = b.dependency("sokol", .{ .target = target, - .optimize = mode, + .optimize = optimize, + .with_sokol_imgui = true, + }); + const dep_cimgui = b.dependency("cimgui", .{ + .target = target, + .optimize = optimize, }); - b.installArtifact(exe); + // inject the cimgui header search path into the sokol C library compile step + const cimgui_root = dep_cimgui.namedWriteFiles("cimgui").getDirectory(); + dep_sokol.artifact("sokol_clib").addIncludePath(cimgui_root); - const run_cmd = try capy.install(exe, .{ .args = b.args }); - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(run_cmd); - - const exe_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = mode, - }); - - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&exe_tests.step); + // from here on different handling for native vs wasm builds + if (target.result.isWasm()) { + try buildWasm(b, target, optimize, dep_sokol, dep_cimgui); + } else { + try buildNative(b, target, optimize, dep_sokol, dep_cimgui); + } +} + +fn buildNative(b: *Build, target: ResolvedTarget, optimize: OptimizeMode, dep_sokol: *Dependency, dep_cimgui: *Dependency) !void { + const demo = b.addExecutable(.{ + .name = "demo", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + demo.root_module.addImport("sokol", dep_sokol.module("sokol")); + demo.root_module.addImport("cimgui", dep_cimgui.module("cimgui")); + b.installArtifact(demo); + b.step("run", "Run demo").dependOn(&b.addRunArtifact(demo).step); +} + +fn buildWasm(b: *Build, target: ResolvedTarget, optimize: OptimizeMode, dep_sokol: *Dependency, dep_cimgui: *Dependency) !void { + // build the main file into a library, this is because the WASM 'exe' + // needs to be linked in a separate build step with the Emscripten linker + const demo = b.addStaticLibrary(.{ + .name = "demo", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + demo.root_module.addImport("sokol", dep_sokol.module("sokol")); + demo.root_module.addImport("cimgui", dep_cimgui.module("cimgui")); + + // get the Emscripten SDK dependency from the sokol dependency + const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); + + // need to inject the Emscripten system header include path into + // the cimgui C library otherwise the C/C++ code won't find + // C stdlib headers + const emsdk_incl_path = dep_emsdk.path("upstream/emscripten/cache/sysroot/include"); + dep_cimgui.artifact("cimgui_clib").addSystemIncludePath(emsdk_incl_path); + + // all C libraries need to depend on the sokol library, when building for + // WASM this makes sure that the Emscripten SDK has been setup before + // C compilation is attempted (since the sokol C library depends on the + // Emscripten SDK setup step) + dep_cimgui.artifact("cimgui_clib").step.dependOn(&dep_sokol.artifact("sokol_clib").step); + + // create a build step which invokes the Emscripten linker + const link_step = try sokol.emLinkStep(b, .{ + .lib_main = demo, + .target = target, + .optimize = optimize, + .emsdk = dep_emsdk, + .use_webgl2 = true, + .use_emmalloc = true, + .use_filesystem = false, + .shell_file_path = dep_sokol.path("src/sokol/web/shell.html").getPath(b), + }); + // ...and a special run step to start the web build output via 'emrun' + const run = sokol.emRunStep(b, .{ .name = "demo", .emsdk = dep_emsdk }); + run.step.dependOn(&link_step.step); + b.step("run", "Run demo").dependOn(&run.step); } diff --git a/build.zig.zon b/build.zig.zon index 7dd480c..51b1b1e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,13 +7,12 @@ "src", }, .dependencies = .{ - .capy = .{ - .url = "https://github.com/ffreling/capy/archive/refs/heads/zig-0.12.0.tar.gz", - .hash = "12207fd2f0d02eb929d1e8a05843de4dc3f4b362055e9e47a014fb6b806a592d9808", + .sokol = .{ + .url = "git+https://github.com/floooh/sokol-zig.git#d9f3ef983b7021417cc91bd5788cd925ca06aa83", + .hash = "12200a472b4f1a3a6db1002f39f10ae0cccf58ada45bf25ddc5fd48384dd187f55ea", }, - .zigimg = .{ - .url = "https://github.com/zigimg/zigimg/archive/8873f29fc449e1b63400e9f4ad86d3c76204f962.tar.gz", - .hash = "122019f6439545235af116d0d8eb81fde1ff05fdb070da57c723772c557f84c5bf39", + .cimgui = .{ + .path = "3rd-party/cimgui", }, }, } diff --git a/flake.nix b/flake.nix index 9cf7396..d321eb0 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,15 @@ zls ]; buildInputs = [ - gtk4 + alsa-lib + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXext + xorg.libXrandr + xorg.libXinerama + libGL + libGLU ]; }; }; diff --git a/src/main.zig b/src/main.zig index b19dee6..a5fe269 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,20 +1,77 @@ -const std = @import("std"); -const capy = @import("capy"); +const ig = @import("cimgui"); +const sokol = @import("sokol"); +const slog = sokol.log; +const sg = sokol.gfx; +const sapp = sokol.app; +const sglue = sokol.glue; +const simgui = sokol.imgui; -// This is required for your app to build to WebAssembly and other particular architectures -pub usingnamespace capy.cross_platform; +const state = struct { + var pass_action: sg.PassAction = .{}; +}; -pub fn main() !void { - try capy.backend.init(); +export fn init() void { + // initialize sokol-gfx + sg.setup(.{ + .environment = sglue.environment(), + .logger = .{ .func = slog.func }, + }); + // initialize sokol-imgui + simgui.setup(.{ + .logger = .{ .func = slog.func }, + }); - var window = try capy.Window.init(); - try window.set( - capy.label(.{ .text = "Hello, World", .alignment = .Center }), - ); - - window.setTitle("Hello"); - window.setPreferredSize(250, 100); - window.show(); - - capy.runEventLoop(); + // initial clear color + state.pass_action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 }, + }; +} + +export fn frame() void { + // call simgui.newFrame() before any ImGui calls + simgui.newFrame(.{ + .width = sapp.width(), + .height = sapp.height(), + .delta_time = sapp.frameDuration(), + .dpi_scale = sapp.dpiScale(), + }); + + //=== UI CODE STARTS HERE + ig.igSetNextWindowPos(.{ .x = 10, .y = 10 }, ig.ImGuiCond_Once, .{ .x = 0, .y = 0 }); + ig.igSetNextWindowSize(.{ .x = 400, .y = 100 }, ig.ImGuiCond_Once); + _ = ig.igBegin("Hello Dear ImGui!", 0, ig.ImGuiWindowFlags_None); + _ = ig.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None); + ig.igEnd(); + //=== UI CODE ENDS HERE + + // call simgui.render() inside a sokol-gfx pass + sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() }); + simgui.render(); + sg.endPass(); + sg.commit(); +} + +export fn cleanup() void { + simgui.shutdown(); + sg.shutdown(); +} + +export fn event(ev: [*c]const sapp.Event) void { + // forward input events to sokol-imgui + _ = simgui.handleEvent(ev.*); +} + +pub fn main() void { + sapp.run(.{ + .init_cb = init, + .frame_cb = frame, + .cleanup_cb = cleanup, + .event_cb = event, + .window_title = "sokol-zig + Dear Imgui", + .width = 800, + .height = 600, + .icon = .{ .sokol_default = true }, + .logger = .{ .func = slog.func }, + }); }