pub const BindingType = enum(u8) { action, axis1d, axis2d, }; const glfw_release = 0; pub const IOEvent = union(enum(u8)) { windowFocused: struct { focused: c_int }, mousePosition: struct { x: f64, y: f64 }, scroll: struct { xoffset: f64, yoffset: f64 }, key: struct { key: Key, scancode: Key, action: ActionEvent, mods: c_int }, mouseRelative: core.Vector2f, windowResize: struct { newSize: core.Vector2f }, codepoint: c_uint, }; pub const ActionEvent = enum(u8) { keyUp, keyDown, keyHeld, }; pub const ActionBindingKey = struct { key: Key, event: ActionEvent = .keyDown, }; fn arenaAlloc() std.mem.Allocator { return getInputStack().arena.allocator(); } fn BindingData(Func: type) type { return struct { pub const Delegate = core.EngineObjectDelegate(Func); keysDown: KeysDownQueue, keysUp: std.ArrayListUnmanaged(Key) = .{}, listeners: std.ArrayListUnmanaged(Delegate) = .{}, listenerId: std.ArrayListUnmanaged(u32) = .{}, consumed: bool = false, routed: bool = false, count: u32 = 0, name: core.Name, pub const KeysDownQueue = core.RingQueueU(struct { key: Key, first: bool = true }); pub fn init(name: core.Name) @This() { return .{ .name = name, .keysDown = KeysDownQueue.init(arenaAlloc(), 256) catch unreachable, }; } // pub fn addListener(self: *@This(), ctx: ?*anyopaque, func: Func) u32 { // const allocator = arenaAlloc(); // self.listeners.append(allocator, .{ .id = self.count, .ctx = ctx, .func = func }) catch unreachable; // self.count +%= 1; // return self.count; // } //pub fn addListener(self: *@This(), ctx:?*anyopaque, func: Func) pub fn addListener(self: *@This(), comptime T: type, comptime funcName: []const u8, target: *anyopaque) u32 { const delegate = Delegate{ .target = target, .func = @field(T, funcName), .version = T.NeonObjectTable.version, .functionName = funcName, .vtable = &T.NeonObjectTable, }; const allocator = arenaAlloc(); self.listeners.append(allocator, delegate) catch unreachable; self.listenerId.append(allocator, self.count) catch unreachable; self.count +%= 1; return self.count; } pub fn removeListener(self: *@This(), id: u32) void { for (self.listeners.items.len, 0..) |listener, i| { if (listener.id == id) { _ = self.listenerId.swapRemove(i); _ = self.listeners.swapRemove(i); return; } } } pub fn setKeyDown(self: *@This(), key: Key) void { self.keysDown.push(.{ .key = key, .first = true }) catch unreachable; } pub fn setKeyHeld(self: *@This(), key: Key) void { self.keysDown.push(.{ .key = key, .first = false }) catch unreachable; } pub fn setKeyUp(self: *@This(), key: Key) void { self.keysUp.append(arenaAlloc(), key) catch unreachable; } pub fn deinit(self: *@This()) void { self.keysDown.deinit(arenaAlloc()); self.listeners.deinit(arenaAlloc()); } }; } pub const ActionBinding = struct { data: BindingData(ActionFunc), keys: std.ArrayListUnmanaged(ActionBindingKey) = .{}, layer: ?*BindingLayer = null, // pub const Listener = struct { // id: u32, // delegate: core. // }; pub fn create(name: core.Name) !*@This() { const allocator = arenaAlloc(); const self = try allocator.create(@This()); self.* = .{ .data = BindingData(ActionFunc).init(name), }; return self; } pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void { try layer.addBindingByName(self.data.name, self); } pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void { try layer.removeBindingByName(self.data.name); } // pushes this binding to the active layer pub fn activate(self: *@This()) void { if (getInputStack().active) |active| { active.addBindingByName(self.data.name, self) catch unreachable; } } pub fn deactivate(self: *@This()) void { if (getInputStack().active) |active| { active.removeBindingByName(self.data.name); } } pub fn addKey(self: *@This(), key: Key, event: ActionEvent) void { const allocator = arenaAlloc(); self.keys.append(allocator, .{ .key = key, .event = event }) catch unreachable; } pub fn deinit(self: *@This()) void { const allocator = arenaAlloc(); self.data.deinit(); self.keys.deinit(allocator); } }; pub const ActionFunc = *const fn (?*anyopaque, ActionEvent) void; pub const AxisBindingKeyMagnitude = struct { key: Key, magnitude: f32 = 1.0, }; pub const Axis1dFunc = *const fn (?*anyopaque, f32) void; pub const Axis1dBinding = struct { data: BindingData(Axis1dFunc), keys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, magnitude: f32 = 0.0, clampValue: ?f32 = 1.0, // pub const Listener = struct { // id: u32, // ctx: ?*anyopaque, // func: Axis1dFunc, // }; pub fn create(name: core.Name) !*@This() { const allocator = arenaAlloc(); const self = try allocator.create(@This()); self.* = .{ .data = BindingData(Axis1dFunc).init(name), }; return self; } pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void { try layer.addBindingByName(self.data.name, self); } pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void { try layer.removeBindingByName(self.data.name); } // pushes this binding to the active layer pub fn activate(self: *@This()) void { if (getInputStack().active) |active| { active.addBindingByName(self.data.name, self) catch unreachable; } } pub fn setClamp(self: *@This(), clampValue: ?f32) void { self.clampValue = clampValue; } pub fn deactivate(self: *@This()) void { if (getInputStack().active) |active| { active.removeBindingByName(self.data.name); } } pub fn addKey(self: *@This(), key: Key, magnitude: f32) void { const allocator = arenaAlloc(); self.keys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable; } pub fn deinit(self: *@This()) void { const allocator = arenaAlloc(); self.data.deinit(); self.keys.deinit(allocator); self.listeners.deinit(allocator); } }; pub const Axis2dFunc = *const fn (?*anyopaque, core.Vector2f) void; pub const Axis2dBinding = struct { data: BindingData(Axis2dFunc), yKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, xKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, // if mouseRelative is set to true then relative mouse movement values shall be passed to the listener // mouseRelative: bool = false, magnitude: core.Vector2f = core.Vector2f.Zeroes, clampValue: ?f32 = 1.0, // pub const Listener = struct { // id: u32, // ctx: ?*anyopaque, // func: Axis2dFunc, // }; pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void { try layer.addBindingByName(self.data.name, self); } pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void { try layer.removeBindingByName(self.data.name); } pub fn create(name: core.Name) !*@This() { const allocator = arenaAlloc(); const self = try allocator.create(@This()); self.* = .{ .data = BindingData(Axis2dFunc).init(name), }; return self; } pub fn disableMouseRelative(self: *@This()) void { self.mouseRelative = false; } pub fn enableMouseRelative(self: *@This()) void { self.mouseRelative = true; self.clampValue = null; } pub fn setClamp(self: *@This(), clampValue: ?f32) void { self.clampValue = clampValue; } // pushes this binding to the active layer pub fn activate(self: *@This()) void { if (getInputStack().active) |active| { active.addBindingByName(self.data.name, self) catch unreachable; } } pub fn deactivate(self: *@This()) void { if (getInputStack().active) |active| { active.removeBindingByName(self.data.name); } } pub fn addKey(self: *@This(), key: Key, magnitude: f32, axis: enum { x, y }) void { const allocator = arenaAlloc(); switch (axis) { .x => { self.xKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable; }, .y => { self.yKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable; }, } } pub fn deinit(self: *@This()) void { const allocator = arenaAlloc(); self.xKeys.deinit(allocator); self.yKeys.deinit(allocator); self.data.deinit(); } }; pub const Binding = union(BindingType) { action: *ActionBinding, axis1d: *Axis1dBinding, axis2d: *Axis2dBinding, pub fn setKeyDown(self: @This(), key: Key) void { switch (self) { .action => |p| { p.data.setKeyDown(key); }, .axis1d => |p| { p.data.setKeyDown(key); }, .axis2d => |p| { p.data.setKeyDown(key); }, } } pub fn setKeyHeld(self: @This(), key: Key) void { switch (self) { .action => |p| { p.data.setKeyHeld(key); }, .axis1d => |p| { p.data.setKeyHeld(key); }, .axis2d => |p| { p.data.setKeyHeld(key); }, } } pub fn setKeyUp(self: @This(), key: Key) void { switch (self) { .action => |p| { p.data.setKeyUp(key); }, .axis1d => |p| { p.data.setKeyUp(key); }, .axis2d => |p| { p.data.setKeyUp(key); }, } } pub fn sendAxisUpdates(self: @This()) void { switch (self) { .action => {}, .axis1d => |p| { p.data.routed = false; for (p.data.listeners.items) |*listener| { listener.call(p.magnitude); } p.magnitude = 0.0; }, .axis2d => |p| { p.data.routed = false; for (p.data.listeners.items) |*listener| { listener.call(p.magnitude); } p.magnitude = core.Vector2f.Zeroes; }, } } pub fn routeBindingEvent(self: @This(), key: Key, event: ActionEvent, consumedOut: *bool) bool { var consumed: bool = false; var routed: bool = false; switch (self) { .action => |p| { for (p.keys.items) |keyBinding| { if (keyBinding.key == key and event == keyBinding.event) { consumed = p.data.consumed; routed = true; } } if (routed) { for (p.data.listeners.items) |*listener| { listener.call(event); } } p.data.routed = routed; }, .axis1d => |p| { for (p.keys.items) |keyBinding| { if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) { p.magnitude += keyBinding.magnitude; consumed = p.data.consumed; routed = true; } } if (p.clampValue) |clampValue| { p.magnitude = std.math.clamp(p.magnitude, -clampValue, clampValue); } p.data.routed = routed; }, .axis2d => |p| { for (p.xKeys.items) |keyBinding| { if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) { p.magnitude.x += keyBinding.magnitude; consumed = p.data.consumed; routed = true; } } for (p.yKeys.items) |keyBinding| { if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) { p.magnitude.y += keyBinding.magnitude; consumed = p.data.consumed; routed = true; } } if (p.clampValue) |clampValue| { p.magnitude.x = std.math.clamp(p.magnitude.x, -clampValue, clampValue); p.magnitude.y = std.math.clamp(p.magnitude.y, -clampValue, clampValue); } p.data.routed = routed; }, } consumedOut.* = consumed; return routed; } fn processKeysHeldDown(self: @This(), p: anytype) void { const keysCount = p.data.keysDown.count(); var i: usize = 0; while (i < keysCount) : (i += 1) { const keydown = p.data.keysDown.pop().?; var isKeyUp: bool = false; for (p.data.keysUp.items) |up| { if (up == keydown.key) { isKeyUp = true; break; } } var consumed: bool = false; if (!isKeyUp and !keydown.first) { _ = self.routeBindingEvent(keydown.key, .keyHeld, &consumed); } if (!isKeyUp) { p.data.setKeyHeld(keydown.key); } } p.data.keysUp.clearRetainingCapacity(); } pub fn processHeldKeys(self: @This()) void { switch (self) { .action => |p| { self.processKeysHeldDown(p); }, .axis1d => |p| { self.processKeysHeldDown(p); }, .axis2d => |p| { self.processKeysHeldDown(p); }, } } pub fn releaseKeys(self: @This()) void { switch (self) { .action => |p| { p.keysDown.clearRetainingCapacity(); }, .axis1d => |p| { p.keysDown.clearRetainingCapacity(); }, .axis2d => |p| { p.keysDown.clearRetainingCapacity(); }, } } }; pub const BindingLayer = struct { allocator: std.mem.Allocator, bindingsByName: std.AutoHashMapUnmanaged(u32, u32) = .{}, bindingStack: std.ArrayListUnmanaged(Binding) = .{}, pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, }; return self; } pub fn addBindingByName(self: *@This(), _name: core.Name, new: anytype) !void { var name = _name; if (self.bindingsByName.contains(name.handle())) { self.removeBindingByName(name); } try self.bindingsByName.put(self.allocator, name.handle(), @intCast(self.bindingStack.items.len)); var newBinding: Binding = undefined; switch (@TypeOf(new)) { *ActionBinding => { newBinding = .{ .action = new }; }, *Axis1dBinding => { newBinding = .{ .axis1d = new }; }, *Axis2dBinding => { newBinding = .{ .axis2d = new }; }, else => { @panic("uh oh"); }, } try self.bindingStack.append(self.allocator, newBinding); } pub fn removeBindingByName(self: *@This(), _name: core.Name) void { var name = _name; const index = self.bindingsByName.get(name.handle()).?; _ = self.bindingStack.orderedRemove(index); _ = self.bindingsByName.remove(name.handle()); var iter = self.bindingsByName.valueIterator(); while (iter.next()) |next| { if (next.* > index) { next.* -= 1; } } } pub fn destroy(self: *@This()) void { self.bindingsByName.deinit(self.allocator); self.bindingStack.deinit(self.allocator); self.allocator.destroy(self); } }; // not gonna actually deal with layers right now pub const InputStack = struct { allocator: std.mem.Allocator, active: ?*BindingLayer, bindingStack: std.ArrayListUnmanaged(*BindingLayer) = .{}, arena: std.heap.ArenaAllocator, keysDown: std.AutoHashMapUnmanaged(Key, bool) = .{}, mousePosition: core.Vector2f = .{}, pub fn setMousePosition(self: *@This(), x: f32, y: f32) void { self.mousePosition.x = x; self.mousePosition.y = y; } pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.InputStack"); pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .arena = std.heap.ArenaAllocator.init(allocator), .active = try BindingLayer.create(allocator), }; core.EngineObject(@This()).gInstance = self; return self; } pub fn updatePreviousInputs(self: *@This()) void { if (self.active == null) { return; } const active = self.active.?; if (active.bindingStack.items.len == 0) { return; } var i: i32 = @intCast(active.bindingStack.items.len - 1); while (i >= 0) : (i -= 1) { const binding = &active.bindingStack.items[@intCast(i)]; binding.processHeldKeys(); } } pub fn sendAxisUpdates(self: *@This()) void { if (self.active == null) { return; } const active = self.active.?; for (active.bindingStack.items) |binding| { binding.sendAxisUpdates(); } } fn routeKeyEvent(self: *@This(), key: Key, event: ActionEvent) void { if (self.active == null) { return; } const active = self.active.?; // for all bindings in reverse order from this layer, route the input, stop the // route if the input was consumed // core.engine_log(" >> {any} > routeKeyEvent 1", .{key}); if (active.bindingStack.items.len == 0) { return; } // core.engine_log(" >> {any} > routeKeyEvent 2", .{key}); var i: i32 = @intCast(active.bindingStack.items.len - 1); // core.engine_log(" >> {any} > routeKeyEvent 2 binding count : {d}", .{ key, i }); while (i >= 0) : (i -= 1) { const binding = active.bindingStack.items[@intCast(i)]; var consumed: bool = false; var routed: bool = false; if (binding.routeBindingEvent(key, event, &consumed)) { routed = true; } if (event == .keyDown) { binding.setKeyDown(key); } else if (event == .keyHeld) { binding.setKeyHeld(key); } else if (event == .keyUp) { binding.setKeyUp(key); } if (consumed) { break; } } } fn routeMouseRelative(self: *@This(), move: core.Vector2f) void { if (self.active == null) { return; } const active = self.active.?; if (active.bindingStack.items.len == 0) { return; } var i: i32 = @intCast(active.bindingStack.items.len - 1); // core.engine_log(" >> {any} > routeKeyEvent 2 binding count : {d}", .{ key, i }); while (i >= 0) : (i -= 1) { const binding = active.bindingStack.items[@intCast(i)]; switch (binding) { // only axis2d handles mouse relative right now, .axis2d => |axis| { if (axis.mouseRelative) { axis.magnitude = axis.magnitude.add(move); } }, else => {}, } } } pub fn routeEvent(self: *@This(), eventToRoute: IOEvent) void { switch (eventToRoute) { .key => |key| { const keyEvent: ActionEvent = key.action; // @enumFromInt(@as(u8, @intCast(key.action))); if (keyEvent != .keyHeld) { // core.engine_log("converted: {any}", .{keyEvent}); self.routeKeyEvent(key.key, keyEvent); // do not route keyHelds } }, .mouseRelative => |rel| { self.routeMouseRelative(rel); }, else => {}, } } pub fn deinit(self: *@This()) void { self.arena.deinit(); if (self.active) |active| { active.destroy(); } self.allocator.destroy(self); } }; pub fn getInputStack() *InputStack { return core.EngineObject(InputStack).get(); } pub fn initInputStack() !void { _ = try core.createObject(InputStack, .{}); } // creates a binding for a lua type // TODO // pub fn LuaBinding(comptime T: type, comptime typeName: []const u8) type { // return struct { // p: ?*T, // // pub const PodDataTable = pod.DataTable{ // .name = typeName, // .funcs = &.{}, // }; // // pub fn create() @This() { // return .{}; // } // }; // } const lua = core.lua; const pod = lua.pod; pub const Key = @import("keys.zig").Key; const std = @import("std"); const core = @import("../core.zig");