diff options
Diffstat (limited to 'src/app/input.zig')
| -rw-r--r-- | src/app/input.zig | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/src/app/input.zig b/src/app/input.zig new file mode 100644 index 0000000..d17199b --- /dev/null +++ b/src/app/input.zig @@ -0,0 +1,281 @@ +const std = @import("std"); + +const root = @import("root"); + +const ArrayListUnmanaged = std.ArrayListUnmanaged; + +pub const Action = @import("app.zig").Action; + +pub const DeviceInput = union(enum) { + pub const PointerId = u8; + pub const GamepadId = u8; + pub const DeviceId = u8; + + pub const PointerAxis = enum {x, y, scroll_x, scroll_y, pressure}; + + key: struct { + code: root.Key + }, + pointer_axis: struct { + pointer_id: PointerId = 0, + axis: PointerAxis, + }, + pointer_button: struct { + pointer_id: PointerId = 0, + button: root.Button, + }, + gamepad_axis: struct { + gamepad_id: GamepadId = 0, + axis: u8, + }, + gamepad_button: struct { + gamepad_id: GamepadId = 0, + button: root.Button, + }, + gyro_axis: struct { + device_id: DeviceId = 0, + axis: enum {x, y, z}, + }, + accel_axis: struct { + device_id: DeviceId = 0, + axis: enum {x, y, z}, + }, + + pub fn eql(self: DeviceInput, other: DeviceInput) bool { + return std.meta.eql(self, other); + } +}; + +pub const InputBinding = struct { + pub const InputState = struct { + input: DeviceInput, + state: bool = false, + }; + + inputs: []InputState, + //input: DeviceInput, + curve: Curve, + action: Action, +}; + +pub const State = struct { + // TODO it should be possible to initialize this to a non-zero value, maybe to curve.eval(0) + current: f32 = 0, + previous: f32 = 0, + + pub fn started(self: State) bool { + return self.previous < 1 and self.current >= 1; + } + pub fn active(self: State) bool { + return self.current >= 1; + } + pub fn ended(self: State) bool { + return self.current < 1 and self.previous >= 1; + } + pub fn value(self: State) f32 { + return self.current; + } + pub fn delta(self: State) f32 { + return self.previous - self.previous; + } +}; +pub fn started(action: Action) bool { + return getState(action).started(); +} +pub fn active(action: Action) bool { + return getState(action).active(); +} +pub fn ended(action: Action) bool { + return getState(action).ended(); +} +pub fn value(action: Action) f32 { + return getState(action).value(); +} +pub fn delta(action: Action) f32 { + return getState(action).delta(); +} + +pub var allocator: std.mem.Allocator = undefined; + +pub var bindings: ArrayListUnmanaged(InputBinding) = undefined; +pub var action_states = [_]State{.{}}**std.meta.fields(Action).len; + +const InitArgs = struct { + allocator: ?std.mem.Allocator = null, + initial_binding_capacity: usize = 16, +}; +pub fn _init(args: InitArgs) !void { + allocator = args.allocator orelse root.allocator; + bindings = try @TypeOf(bindings).initCapacity(allocator, args.initial_binding_capacity); + errdefer bindings.deinit(allocator); +} + +pub fn _deinit() void { + for (bindings.items) |*binding| { + allocator.free(binding.inputs); + if (binding.curve.keys) |keys| allocator.free(keys); + } + bindings.deinit(allocator); +} + +pub fn _before_poll() void { + for (&action_states) |*state| { + state.previous = state.current; + } +} + +pub fn getState(action: Action) *State { + return &action_states[@intFromEnum(action)]; +} + +pub fn simulate(input: DeviceInput, raw_value: f32) void { + // FIXME if we have Ctrl+A = .foo and A = .bar, then start with A, and later hold Ctrl, both .foo and .bar are active + var winner: ?*InputBinding = null; + for (bindings.items) |*binding| { + var hit = false; + var state = true; + for (binding.inputs) |*binding_input| { + if (binding_input.input.eql(input)) { + hit = true; + binding_input.state = raw_value != 0; + break; + } + state &= binding_input.state; + } + if (!hit) continue; + if (winner != null and binding.inputs.len <= winner.?.inputs.len) continue; + if (!state) continue; + winner = binding; + } + + if (winner == null) return; + + for (winner.?.inputs) |is| { + if (raw_value != 0 and !is.state) return; + } + + const action_state = getState(winner.?.action); + action_state.*.current = winner.?.curve.eval(raw_value); +} + +pub fn bind(action: Action, input: []const DeviceInput, curve: Curve) !void { + const input_states = try allocator.alloc(InputBinding.InputState, input.len); + errdefer allocator.free(input_states); + for (input_states, input) |*input_state, device_input| { + input_state.* = .{ + .input = device_input, + .state = switch (device_input) {.key, .pointer_button => false, else => true}, + }; + } + try bindings.append(allocator, .{ + .inputs = input_states, + .curve = curve, + .action = action, + }); +} +pub fn bindKey(action: Action, key: CommonKey) !void { + try bind(action, &.{.{.key = .{.code = root.getPlatformKey(key)}}}, Curve.linear()); +} +pub fn bindPointerAxis(action: Action, axis: DeviceInput.PointerAxis) !void { + try bind(action, &.{.{.pointer_axis = .{.axis = axis}}}, Curve.linear()); +} +pub fn bindPointerButton(action: Action, button: CommonButton) !void { + try bind(action, &.{.{.pointer_button = .{.button = root.getPlatformButton(button)}}}, Curve.linear()); +} + + + + + +pub const Curve = struct { + pub const Key = struct { + x: f32, + y: f32, + in_tangent: f32, + out_tangent: f32, + }; + + keys: ?[]Key, + + pub fn init(keys: []Key) !Curve { + const var_keys = try allocator.alloc(Key, keys.len); + errdefer allocator.free(var_keys); + @memcpy(var_keys, keys); + return .{ + .keys = var_keys, + }; + } + + pub fn eval(c: Curve, x: f32) f32 { + if (c.keys == null) return x; + + const n = c.keys.?.len; + if (n == 0) return 0; + + var i: usize = 0; + + if (x <= c.keys.?[0].x) { + i = 0; + } else if (x >= c.keys.?[n-1].x) { + i = n - 2; + } else { + while (i+1 < n and x > c.keys.?[i+1].x) : (i += 1) {} + } + + + // https://en.wikipedia.org/wiki/Cubic_Hermite_spline + + const k0 = c.keys.?[i]; + const k1 = c.keys.?[i+1]; + + const t = (x - k0.x) / (k1.x - k0.x); + const t2 = t*t; + const t3 = t2*t; + + const h00 = 2*t3 - 3*t2 + 1; + const h10 = t3 - 2*t2 + t; + const h01 = -2*t3 + 3*t2; + const h11 = t3 - t2; + + const dx = k1.x - k0.x; + + return + h00 * k0.y + + h10 * k0.out_tangent * dx + + h01 * k1.y + + h11 * k1.in_tangent * dx; + } + + pub fn linear() Curve { + return .{.keys = null}; + } + + // TODO + //pub fn invert(c: *const Curve) *const Curve { + // for (c.keys) |*k| { + // k.y = 1 - k.y; + // } + // return c; + //} + + //pub fn deadzone(c: *const Curve, min_activation: f32) *const Curve { + // for (c.keys) |*k| { + // if (k.* >= min_activation) return; + // k.* = 0; + // } + // return c; + //} +}; + +pub const CommonKey = enum { + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + left_control, right_control, + left_shift, right_shift, + arrow_left, arrow_right, arrow_up, arrow_down, +}; +pub const CommonButton = enum { + left, + middle, + right, +}; + |
