const ona = @import("ona"); const std = @import("std"); pub const Axis = struct { has_key_scancodes: ?[2]KeyScancode = null, has_mouse_buttons: ?[2]MouseButton = null, }; pub const Event = union (enum) { key_up: KeyScancode, key_down: KeyScancode, mouse_up: MouseButton, mouse_down: MouseButton, mouse_motion: MouseMotion, }; pub const KeyScancode = enum (u32) { no_event = 0x00, error_rollover = 0x01, post_fail = 0x02, error_undefined = 0x03, a = 0x04, b = 0x05, c = 0x06, d = 0x07, e = 0x08, f = 0x09, g = 0x0A, h = 0x0B, i = 0x0C, j = 0x0D, k = 0x0E, l = 0x0F, m = 0x10, n = 0x11, o = 0x12, p = 0x13, q = 0x14, r = 0x15, s = 0x16, t = 0x17, u = 0x18, v = 0x19, w = 0x1A, x = 0x1B, y = 0x1C, z = 0x1D, one = 0x1E, two = 0x1F, three = 0x20, four = 0x21, five = 0x22, six = 0x23, seven = 0x24, eight = 0x25, nine = 0x26, zero = 0x27, enter = 0x28, escape = 0x29, backspace = 0x2A, tab = 0x2B, space = 0x2C, minus = 0x2D, equal = 0x2E, left_bracket = 0x2F, right_bracket = 0x30, backslash = 0x31, non_us_pound = 0x32, semicolon = 0x33, quote = 0x34, grave = 0x35, comma = 0x36, period = 0x37, slash = 0x38, caps_lock = 0x39, f1 = 0x3A, f2 = 0x3B, f3 = 0x3C, f4 = 0x3D, f5 = 0x3E, f6 = 0x3F, f7 = 0x40, f8 = 0x41, f9 = 0x42, f10 = 0x43, f11 = 0x44, f12 = 0x45, print_screen = 0x46, scroll_lock = 0x47, pause = 0x48, insert = 0x49, home = 0x4A, page_up = 0x4B, delete = 0x4C, end = 0x4D, page_down = 0x4E, right_arrow = 0x4F, left_arrow = 0x50, down_arrow = 0x51, up_arrow = 0x52, num_lock = 0x53, keypad_slash = 0x54, keypad_asterisk = 0x55, keypad_minus = 0x56, keypad_plus = 0x57, keypad_enter = 0x58, keypad_one = 0x59, keypad_two = 0x5A, keypad_three = 0x5B, keypad_four = 0x5C, keypad_five = 0x5D, keypad_six = 0x5E, keypad_seven = 0x5F, keypad_eight = 0x60, keypad_nine = 0x61, keypad_zero = 0x62, keypad_period = 0x63, non_us_backslash = 0x64, application = 0x65, power = 0x66, keypad_equal = 0x67, f13 = 0x68, f14 = 0x69, f15 = 0x6A, f16 = 0x6B, f17 = 0x6C, f18 = 0x6D, f19 = 0x6E, f20 = 0x6F, f21 = 0x70, f22 = 0x71, f23 = 0x72, f24 = 0x73, execute = 0x74, help = 0x75, menu = 0x76, select = 0x77, stop = 0x78, again = 0x79, undo = 0x7A, cut = 0x7B, copy = 0x7C, paste = 0x7D, find = 0x7E, mute = 0x7F, volume_up = 0x80, volume_down = 0x81, lock_caps_lock = 0x82, lock_num_lock = 0x83, lock_scroll_lock = 0x84, keypad_comma = 0x85, keypad_equal_sign = 0x86, international1 = 0x87, international2 = 0x88, international3 = 0x89, international4 = 0x8A, international5 = 0x8B, international6 = 0x8C, international7 = 0x8D, international8 = 0x8E, international9 = 0x8F, lang1 = 0x90, lang2 = 0x91, lang3 = 0x92, lang4 = 0x93, lang5 = 0x94, lang6 = 0x95, lang7 = 0x96, lang8 = 0x97, lang9 = 0x98, alternate_erase = 0x99, sys_req_attention = 0x9A, cancel = 0x9B, clear = 0x9C, prior = 0x9D, return_key = 0x9E, separator = 0x9F, out = 0xA0, oper = 0xA1, clear_again = 0xA2, cr_sel_props = 0xA3, ex_sel = 0xA4, left_control = 0xE0, left_shift = 0xE1, left_alt = 0xE2, left_gui = 0xE3, right_control = 0xE4, right_shift = 0xE5, right_alt = 0xE6, right_gui = 0xE7, _, }; pub const Mapping = struct { key_scancodes: ActionSets(512) = .{}, mouse_buttons: ActionSets(std.enums.values(MouseButton).len) = .{}, mouse_position: MousePosition = @splat(0), fn ActionSets(comptime len: usize) type { const Set = std.bit_set.StaticBitSet(len); return struct { released: Set = Set.initEmpty(), pressed: Set = Set.initEmpty(), held: Set = Set.initEmpty(), const Self = @This(); fn expire_frame(self: *Self) void { self.pressed = Set.initEmpty(); self.released = Set.initEmpty(); } }; } pub fn axis_strength(self: Mapping, axis: Axis) f32 { if (axis.has_key_scancodes) |key_scancodes| { const neg_scancode, const pos_scancode = key_scancodes; const is_neg_held = self.key_scancodes.held.isSet(@intFromEnum(neg_scancode)); const is_pos_held = self.key_scancodes.held.isSet(@intFromEnum(pos_scancode)); if (is_neg_held or is_pos_held) { return @as(f32, @floatFromInt(@intFromBool(is_pos_held))) - @as(f32, @floatFromInt(@intFromBool(is_neg_held))); } } if (axis.has_mouse_buttons) |mouse_buttons| { const neg_button, const pos_button = mouse_buttons; const is_neg_held = self.mouse_buttons.held.isSet(@intFromEnum(neg_button)); const is_pos_held = self.mouse_buttons.held.isSet(@intFromEnum(pos_button)); if (is_neg_held or is_pos_held) { return @as(f32, @floatFromInt(@intFromBool(is_pos_held))) - @as(f32, @floatFromInt(@intFromBool(is_neg_held))); } } return 0; } }; pub const MouseButton = enum { left, right, middle, }; pub const MouseMotion = struct { relative_position: MousePosition, absolute_position: MousePosition, }; pub const MousePosition = @Vector(2, f32); test "mapping values" { const axis = Axis{ .has_key_scancodes = .{.minus, .equal}, }; { var mapping = Mapping{}; try std.testing.expectEqual(mapping.axis_strength(axis), 0); } { var mapping = Mapping{}; mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.equal)); try std.testing.expectEqual(mapping.axis_strength(axis), 1); } { var mapping = Mapping{}; mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.minus)); try std.testing.expectEqual(mapping.axis_strength(axis), -1); } { var mapping = Mapping{}; mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.minus)); mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.equal)); try std.testing.expectEqual(mapping.axis_strength(axis), 0); } } pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void { try world.set_state(Mapping{}); try world.on_event(events.pre_update, ona.system_fn(update), .{ .label = "update actions", }); } pub fn update(inputs: ona.Receive(Event), mapping: ona.Write(Mapping)) void { mapping.state.key_scancodes.expire_frame(); mapping.state.mouse_buttons.expire_frame(); for (inputs.messages()) |message| { switch (message) { .key_down => |scancode| { mapping.state.key_scancodes.pressed.set(@intFromEnum(scancode)); mapping.state.key_scancodes.held.set(@intFromEnum(scancode)); }, .key_up => |scancode| { mapping.state.key_scancodes.held.unset(@intFromEnum(scancode)); mapping.state.key_scancodes.released.set(@intFromEnum(scancode)); }, .mouse_down => |button| { mapping.state.mouse_buttons.pressed.unset(@intFromEnum(button)); mapping.state.mouse_buttons.held.set(@intFromEnum(button)); }, .mouse_up => |button| { mapping.state.key_scancodes.held.unset(@intFromEnum(button)); mapping.state.key_scancodes.released.set(@intFromEnum(button)); }, .mouse_motion => |motion| { mapping.state.mouse_position = motion.absolute_position; }, } } }