This commit is contained in:
Dario48 2026-06-13 15:08:28 +02:00
commit b38a3b7bda
9 changed files with 801 additions and 0 deletions

6
src/c.h Normal file
View file

@ -0,0 +1,6 @@
#define GLFW_INCLUDE_NONE 1
#include "GL/gl.h"
#include "GLFW/glfw3.h"
#include "backends/dcimgui_impl_glfw.h"
#include "backends/dcimgui_impl_opengl3.h"
#include "dcimgui.h"

279
src/main.zig Normal file
View file

@ -0,0 +1,279 @@
const std = @import("std");
const c = @import("imgui");
const CError = error{cError};
fn wrap(@"fn": anytype, fnArgs: anytype) CError!void {
const ret = @call(.auto, @"fn", fnArgs);
if (ret != 0) {
std.log.err("function " ++ @typeName(@TypeOf(@"fn")) ++ " returned code {}", .{ret});
return error.cError;
}
}
fn setFlags(flags: []const c_int, io: *c.struct_ImGuiIO_t) void {
for (flags) |flag| {
io.ConfigFlags |= flag;
}
}
export fn glfw_error_callback(@"error": c_int, description: [*c]const u8) void {
std.log.err("GLFW error {}: {s}", .{ @"error", description });
}
pub const std_options: std.Options = .{};
fn formatArgv(alloc: std.mem.Allocator, format: []const u8, path: []const u8) ![][]u8 {
var outer = std.ArrayList([]u8).empty;
errdefer {
for (outer.items) |arr| {
alloc.free(arr);
}
outer.deinit(alloc);
}
var inner = std.ArrayList(u8).empty;
errdefer inner.deinit(alloc);
var quoted: enum {
@"\"",
@"'",
no,
pub fn toBool(self: @This()) bool {
return switch (self) {
.@"\"", .@"'" => true,
else => false,
};
}
pub inline fn fromU8(char: u8) @This() {
return switch (char) {
'"' => .@"\"",
'\'' => .@"'",
else => unreachable,
};
}
} = .no;
var escaped: bool = false;
for (format) |char| {
switch (char) {
inline '\'', '"' => |q| {
if (escaped)
try inner.append(alloc, q)
else {
const tmp: @TypeOf(quoted) = .fromU8(q);
if (quoted.toBool()) {
if (tmp == quoted)
quoted = .no
else
try inner.append(alloc, q);
} else quoted = tmp;
}
},
'\\' => {
if (escaped)
try inner.append(alloc, '\\')
else
escaped = true;
},
's' => try if (escaped) inner.appendSlice(alloc, path) else inner.append(alloc, 's'),
'\t'...'\r', ' ' => |w| {
if (escaped or quoted.toBool()) {
try inner.append(alloc, w);
} else {
const owned = try inner.toOwnedSlice(alloc);
errdefer alloc.free(owned);
try outer.append(alloc, owned);
}
},
else => |g| try inner.append(alloc, g),
}
if (escaped) escaped = false;
}
return outer.toOwnedSlice(alloc);
}
const opts = struct {
vsync: bool = true,
fullscreen: bool = false,
light: bool = false,
classic: bool = false,
width: f32 = 1280,
height: f32 = 800,
zonPath: [:0]const u8 = ".",
};
var args: opts = .{};
const map = blk: {
var boolFields: []const struct { []const u8, *bool } = &.{};
var floatFields: []const struct { []const u8, *f32 } = &.{};
for (@typeInfo(opts).@"struct".fields) |field| {
switch (field.type) {
bool => {
boolFields = .{.{ "--" ++ field.name, &@field(args, field.name) }} ++ boolFields;
boolFields = .{.{ "-" ++ .{field.name[0]}, &@field(args, field.name) }} ++ boolFields;
},
f32 => {
floatFields = .{.{ "--" ++ field.name, &@field(args, field.name) }} ++ floatFields;
floatFields = .{.{ "-" ++ .{field.name[0]}, &@field(args, field.name) }} ++ floatFields;
},
else => {},
}
}
break :blk .{ std.StaticStringMap(*bool).initComptime(boolFields), std.StaticStringMap(*f32).initComptime(floatFields) };
};
const binOptMap = map[0];
const floatOptMap = map[1];
var argv: ?[][]u8 = null;
pub fn main(init: std.process.Init) !void {
{
var iter = try init.minimal.args.iterateAllocator(init.gpa);
defer iter.deinit();
_ = iter.next().?;
while (iter.next()) |i| {
if (binOptMap.get(i)) |o|
o.* = true
else {
if (std.mem.cutScalar(u8, i, '=')) |v| {
const opt, const value = v;
if (floatOptMap.get(opt)) |o|
o.* = try std.fmt.parseFloat(f32, value);
}
}
}
const file = std.Io.Dir.cwd().openFile(init.io, args.zonPath, .{ .allow_directory = false }) catch |err| switch (err) {
error.IsDir => blk: {
const dir = try std.Io.Dir.cwd().openDir(init.io, args.zonPath, .{});
defer dir.close(init.io);
break :blk try dir.openFile(init.io, "rom_list.zon", .{});
},
else => |e| return e,
};
defer file.close(init.io);
var buf: [1024]u8 = undefined;
var reader = file.reader(init.io, &buf);
const content = try reader.interface.allocRemainingAlignedSentinel(init.gpa, .unlimited, .of(u8), 0);
var diag: std.zon.parse.Diagnostics = .{};
const zon = std.zon.parse.fromSliceAlloc(struct {
emus: []struct {
name: []const u8,
exec_format: []const u8,
},
roms: []struct {
name: [:0]const u8,
emu: []const u8,
path: []const u8,
},
}, init.gpa, content, &diag, .{}) catch |err| {
std.log.err("{f}", .{diag});
return err;
};
defer std.zon.parse.free(init.gpa, zon);
_ = c.glfwSetErrorCallback(glfw_error_callback);
try wrap(c.glfwInit, .{});
defer c.glfwTerminate();
const glsl_version = "#version 300 es";
c.glfwWindowHint(c.GLFW_VERSION_MAJOR, 3);
c.glfwWindowHint(c.GLFW_VERSION_MINOR, 3);
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_OPENGL_ES_API);
const main_scale = c.cImGui_ImplGlfw_GetContentScaleForMonitor(c.glfwGetPrimaryMonitor());
const window = c.glfwCreateWindow(@trunc(args.width * main_scale), @trunc(args.height * main_scale), "choose rom to execute", c.glfwGetPrimaryMonitor(), null) orelse return error.GLFWWindowInitErr;
defer c.glfwDestroyWindow(window);
c.glfwMakeContextCurrent(window);
c.glfwSwapInterval(@intFromBool(args.vsync)); // enable vsync
if (!c.CIMGUI_CHECKVERSION())
return error.imguiVersionError;
const ctx = c.ImGui_CreateContext(null);
defer c.ImGui_DestroyContext(ctx);
const imguiIO = c.ImGui_GetIO();
setFlags(&.{ c.ImGuiConfigFlags_NavEnableKeyboard, c.ImGuiConfigFlags_NavEnableGamepad }, imguiIO);
const style = c.ImGui_GetStyle();
if (args.classic)
c.ImGui_StyleColorsClassic(style)
else if (args.light)
c.ImGui_StyleColorsLight(style)
else
c.ImGui_StyleColorsDark(style);
c.ImGuiStyle_ScaleAllSizes(style, main_scale);
style.*.FontScaleDpi = main_scale;
if (!c.cImGui_ImplGlfw_InitForOpenGL(window, true)) return error.GLFWInit;
defer c.cImGui_ImplGlfw_Shutdown();
if (!c.cImGui_ImplOpenGL3_InitEx(glsl_version)) return error.OpenGlInit;
defer c.cImGui_ImplOpenGL3_Shutdown();
const clear_color = c.ImVec4{ .x = 0.45, .y = 0.55, .w = 0.60, .z = 1 };
while (c.glfwWindowShouldClose(window) == 0) {
c.glfwPollEvents();
if (c.glfwGetWindowAttrib(window, c.GLFW_ICONIFIED) != 0) {
c.cImGui_ImplGlfw_Sleep(10);
}
c.cImGui_ImplOpenGL3_NewFrame();
c.cImGui_ImplGlfw_NewFrame();
c.ImGui_NewFrame();
{
if (!c.ImGui_Begin("console", null, 0)) return error.BeginError;
defer c.ImGui_End();
var err: anyerror = error.NoError;
errdefer |encounteredErr| {
err = encounteredErr;
c.ImGui_OpenPopup("errPopup", 0);
}
{
if (c.ImGui_BeginPopup("errPopUp", 0)) {
defer c.ImGui_EndPopup();
c.ImGui_Text("encontered error '%s'", @errorName(err).ptr);
}
}
for (zon.roms) |rom| {
if (c.ImGui_Button(rom.name.ptr)) {
for (zon.emus) |emu|
if (std.mem.eql(u8, emu.name, rom.emu)) {
c.glfwSetWindowShouldClose(window, 1);
argv = try formatArgv(init.arena.allocator(), emu.exec_format, rom.name);
};
return error.InvalidEmu;
}
}
}
c.ImGui_Render();
var w: c_int = undefined;
var h: c_int = undefined;
c.glfwGetFramebufferSize(window, &w, &h);
c.glViewport(0, 0, w, h);
c.glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
c.glClear(c.GL_COLOR_BUFFER_BIT);
c.cImGui_ImplOpenGL3_RenderDrawData(c.ImGui_GetDrawData());
c.glfwSwapBuffers(window);
}
}
if (argv) |a| {
return std.process.replace(init.io, .{ .argv = a });
}
}