const std = @import("std"); const core = @import("core"); const platform = @import("platform.zig"); const RingQueue = core.RingQueue; pub const sdl3 = @import("sdl3"); const tracy = core.tracy; pub const InputListenerError = error{ UnknownError, }; 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 }, 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; } }; pub var gPlatformSettings: struct { pollLockoutTime: ?f32 = null, decoratedWindow: bool = true, transparentFrameBuffer: bool = false, } = .{}; // 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, extent: core.Vector2f, windowName: [:0]u8, iconPath: [:0]u8, enableImguiEvents: bool = true, imguiVisible: bool = true, windowDestroyed: bool = false, processFuncs: std.ArrayListUnmanaged(*const fn (*sdl3.Event) void) = .{}, cursorPos: core.Vector2f = .{}, pub fn deinit(self: *@This()) void { self.allocator.free(self.windowName); self.allocator.free(self.iconPath); self.processFuncs.deinit(self.allocator); // 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, params: PlatformParams, ) !@This() { const self: @This() = .{ .allocator = 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 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.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, 0).?; // resizeable self.extent = .{ .x = @floatFromInt(self.windowExtent.x), .y = @floatFromInt(self.windowExtent.y), }; } pub fn registerFuncs(self: *@This()) !void { core.setupEnginePlatform(self, enginePoll, processEvents); } // runs pinned on io thread. // old glfw needed to do it's polling here. pub fn enginePoll(opaqueSelf: *anyopaque) core.EngineDataEventError!void { _ = opaqueSelf; // 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 processEvents(ptr: *anyopaque, frameNumber: u64) core.EngineDataEventError!void { _ = frameNumber; const self: *@This() = @ptrCast(@alignCast(ptr)); // 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 (core.inputs.convertEvent(event)) |converted| { inputStack.routeEvent(converted); } for (self.processFuncs.items) |func| { func(@ptrCast(event)); } switch (event.type) { sdl3.events.quit => { core.exitNow(); }, else => {}, } } inputStack.sendAxisUpdates(); } pub fn getCursorPosition(self: *@This()) core.Vector2f { return self.cursorPos; } pub fn setCursorEnabled(self: *@This(), cursorEnabled: bool) void { self.newCursorEnabled = cursorEnabled; } pub fn isCursorEnabled(self: @This()) bool { return self.cursorEnabled; } pub fn setCursorVisible(self: @This(), visible: bool) void { _ = self; if (visible) { sdl3.showCursor(); } else { sdl3.hideCursor(); } } }; pub const IOEvent = core.IOEvent;