Backlog/engine/platform/src/windowing.zig

362 lines
12 KiB
Zig

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);
}
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(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
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,
};
}
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;