const std = @import("std"); const core = @import("core"); const platform = @import("platform.zig"); const nfd = @import("nfd"); const RingQueue = core.RingQueue; pub const sdl3 = @import("sdl3"); const tracy = core.tracy; pub const InputListenerError = error{ UnknownError, }; pub fn convertEvent(event: *const sdl3.Event) ?core.inputs.IOEvent { switch (event.type) { sdl3.events.key_down, sdl3.events.key_up => { const scan: core.inputs.Key = @enumFromInt(event.key.scancode); if (event.key.repeat) { return null; } return IOEvent{ .key = .{ .key = scan, .scancode = scan, .action = if (event.key.down) .keyDown else .keyUp, .mods = 0, }, }; }, sdl3.events.mouse_button_down, sdl3.events.mouse_button_up => { const scan: core.inputs.Key = @enumFromInt(@as(u32, @intCast(event.button.button)) + 10000); return IOEvent{ .key = .{ .key = scan, .scancode = scan, .action = if (event.button.down) .keyDown else .keyUp, .mods = 0, }, }; }, sdl3.events.mouse_motion => { const motion = core.Vector2f{ .x = event.motion.xrel, .y = event.motion.yrel }; return .{ .mouseRelative = motion }; }, else => { return null; }, } return null; } pub const RawInputListenerInterface = struct { // required functions OnIoEvent: *const fn (*anyopaque, event: IOEvent) InputListenerError!void, pub fn from(comptime TargetType: type) @This() { const Wrapped = struct { pub fn OnIoEvent(pointer: *anyopaque, event: IOEvent) InputListenerError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); try ptr.OnIoEvent(event); } }; return .{ .OnIoEvent = Wrapped.OnIoEvent, }; } }; pub const RawInputObjectRef = struct { ptr: *anyopaque, vtable: *const RawInputListenerInterface, pub fn from(target: anytype) @This() { const vtable = &(@TypeOf(target.*)).RawInputListenerVTable; return .{ .ptr = target, .vtable = vtable, }; } }; // We are having some serious issues with this events pump. pub const PlatformParams = struct { extent: core.Vector2c = .{ .x = 1600, .y = 900 }, resizeable: bool = true, windowName: []const u8, icon: []const u8 = "textures/icon.png", hasVideo: bool = true, pub fn init() @This() { const rv = @This(){ .extent = .{ .x = core.configVar(c_int, "platform.window.width", 1600), .y = core.configVar(c_int, "platform.window.height", 900), }, .windowName = core.configVar([]const u8, "platform.window.name", "Backlog Engine"), .icon = core.configVar([]const u8, "platform.window.icon", "textures/icon.png"), }; core.engine_log("platform settings {any}", .{rv}); return rv; } }; // For windows and linux computers this is a glfw instance pub const PlatformInstance = struct { allocator: std.mem.Allocator, cursorEnabled: bool = true, newCursorEnabled: bool = true, hasVideo: bool = false, window: *sdl3.Window = undefined, windowExtent: core.Vector2c = undefined, extent: core.Vector2f = undefined, windowName: [:0]u8 = undefined, iconPath: [:0]u8 = undefined, enableImguiEvents: bool = true, imguiVisible: bool = true, imguiMouseConsumed: bool = false, windowDestroyed: bool = false, processFuncs: std.ArrayListUnmanaged(*const fn (*sdl3.Event) void) = .{}, platformRequests: std.ArrayListUnmanaged(PlatformRequest) = .{}, cursorPos: core.Vector2f = .{}, nfdRuntime: *nfd.NFDRuntime, pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "platform.Instance"); pub const PlatformRequest = union(enum(u32)) { setMouseRelativeMode: struct { relativeMode: bool }, setMousePosition: struct { pos: core.Vector2f }, setCursorVisible: struct { visible: bool }, setWindowSize: struct { size: core.Vector2u }, maximizeWindow: struct {}, setWindowPosition: struct { pos: core.Vector2u }, }; pub fn pushRequest(self: *@This(), req: PlatformRequest) void { self.platformRequests.append(self.allocator, req) catch { core.engine_err("unable to issue request", .{}); }; } pub fn handlePlatformRequests(self: *@This()) void { for (self.platformRequests.items) |req| { switch (req) { .setMouseRelativeMode => |p| { self.setMouseRelativeMode(p.relativeMode); }, .setMousePosition => |p| { self.setMousePosition(p.pos); }, .setCursorVisible => |p| { self.setCursorVisible(p.visible); }, .setWindowSize => |p| { self.setWindowSize(p.size); }, .maximizeWindow => { self.maximizeWindow(); }, .setWindowPosition => |p| { self.setWindowPosition(p.pos); }, } } self.platformRequests.clearRetainingCapacity(); } pub fn deinit(self: *@This()) void { self.allocator.free(self.windowName); self.nfdRuntime.destroy(); self.allocator.free(self.iconPath); self.processFuncs.deinit(self.allocator); self.platformRequests.deinit(self.allocator); self.allocator.destroy(self); // shutdown } pub fn onExitSignal(self: *@This()) !void { sdl3.c.SDL_DestroyWindow(self.window); self.windowDestroyed = true; } pub fn readyToExit(self: *@This()) bool { return self.windowDestroyed; } pub fn init( allocator: std.mem.Allocator, ) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .nfdRuntime = try nfd.NFDRuntime.create(allocator, .{}), //.windowName = try allocator.dupeZ(u8, params.windowName), //.iconPath = try allocator.dupeZ(u8, params.icon), //.windowExtent = params.extent, //.extent = .{ .x = @floatFromInt(params.extent.x), .y = @floatFromInt(params.extent.y) }, //.hasVideo = params.hasVideo, }; return self; } pub fn setParams(self: *@This(), params: PlatformParams) !void { self.windowName = try self.allocator.dupeZ(u8, params.windowName); self.iconPath = try self.allocator.dupeZ(u8, params.icon); self.windowExtent = params.extent; self.extent = .{ .x = @floatFromInt(params.extent.x), .y = @floatFromInt(params.extent.y) }; self.hasVideo = params.hasVideo; } pub fn setMouseRelativeMode(self: *@This(), relativeMode: bool) void { // _ = sdl3.c.SDL_SetHint(sdl3.c.SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "1"); _ = sdl3.c.SDL_SetWindowRelativeMouseMode(self.window, relativeMode); _ = sdl3.c.SDL_SetWindowMouseRect(self.window, null); self.cursorEnabled = !relativeMode; self.enableImguiEvents = !relativeMode; } pub fn addSDLProcessFunction(self: *@This(), processFunc: *const fn (*sdl3.Event) void) !void { try self.processFuncs.append(self.allocator, processFunc); } pub fn setupWindow(self: *@This()) core.EngineDataEventError!void { sdl3.init(.{ .video = true, .gamepad = true }) catch return error.BadInit; self.window = sdl3.c.SDL_CreateWindow(self.windowName, self.windowExtent.x, self.windowExtent.y, sdl3.c.SDL_WINDOW_RESIZABLE).?; // resizeable self.extent = .{ .x = @floatFromInt(self.windowExtent.x), .y = @floatFromInt(self.windowExtent.y), }; } pub fn registerFuncs(self: *@This()) !void { core.setupEnginePlatform(self, enginePoll, procEvents); } // runs pinned on io thread. // old glfw needed to do it's polling here. pub fn enginePoll(opaqueSelf: *anyopaque) core.EngineDataEventError!void { const self = core.cast(*@This(), opaqueSelf); self.nfdRuntime.processMessages() catch {}; // this polling event should probably be used for IO, now that we have a free thread for doing w.e // var self: *@This() = @alignCast(@ptrCast(opaqueSelf)); // self.pollEvents(); } pub fn installListener(self: *@This(), listener: anytype) !void { try self.listeners.append(RawInputObjectRef.from(listener)); } pub fn setMousePosition(self: *@This(), pos: core.Vector2f) void { sdl3.c.SDL_WarpMouseInWindow(self.window, pos.x, pos.y); } pub fn procEvents(ptr: *anyopaque, frameNumber: u64) core.EngineDataEventError!void { _ = frameNumber; const self: *@This() = @ptrCast(@alignCast(ptr)); self.nfdRuntime.processCallbacks(); self.handlePlatformRequests(); // const self: *@This() = @ptrCast(@alignCast(ptr)); var t1 = tracy.ZoneN(@src(), "Pumping Events"); defer t1.End(); const inputStack = core.getInputStack(); inputStack.updatePreviousInputs(); while (sdl3.pollEvent()) |event| { if (event.type == sdl3.events.mouse_motion) { sdl3.getMouseState(&self.cursorPos.x, &self.cursorPos.y); } if (event.type == sdl3.events.window_resized) { self.extent.x = @floatFromInt(event.window.data1); self.extent.y = @floatFromInt(event.window.data2); core.engine_log("engine resized {d}x{d}", self.extent); } for (self.processFuncs.items) |func| { func(@ptrCast(event)); } if (convertEvent(event)) |converted| { var shouldSkip: bool = false; if (self.imguiMouseConsumed and (event.type == sdl3.events.mouse_button_down or event.type == sdl3.events.mouse_button_up)) { shouldSkip = true; } if (!shouldSkip) { inputStack.routeEvent(converted); if (event.type == sdl3.events.mouse_motion) { inputStack.setMousePosition(self.cursorPos.x, self.cursorPos.y); } } } switch (event.type) { sdl3.events.quit => { core.exitNow(); }, else => {}, } } inputStack.sendAxisUpdates(); } pub fn getCursorPosition(self: *@This()) core.Vector2f { return self.cursorPos; } pub fn isCursorEnabled(self: @This()) bool { return self.cursorEnabled; } pub fn setCursorVisible(self: *@This(), visible: bool) void { self.cursorEnabled = visible; if (visible) { sdl3.showCursor(); } else { sdl3.hideCursor(); } } pub fn setWindowSize(self: *@This(), size: core.Vector2u) void { if (sdl3.c.SDL_SetWindowSize(self.window, @intCast(size.x), @intCast(size.y))) { _ = sdl3.c.SDL_SyncWindow(self.window); } else { core.engine_log("unable to set window size {s}", .{sdl3.getError()}); } } pub fn maximizeWindow(self: *@This()) void { _ = sdl3.c.SDL_MaximizeWindow(self.window); } pub fn setWindowPosition(self: *@This(), pos: core.Vector2u) void { _ = sdl3.c.SDL_SetWindowPosition(self.window, @intCast(pos.x), @intCast(pos.y)); } }; pub const IOEvent = core.IOEvent;