summaryrefslogtreecommitdiff
path: root/src/app/input.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/input.zig')
-rw-r--r--src/app/input.zig281
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,
+};
+