summaryrefslogtreecommitdiffstats
path: root/src/core/frontend/input.h
blob: 7a047803ee1e45091a41c2c6ebde12d1f95ab1c1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#pragma once

#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/quaternion.h"
#include "common/vector_math.h"

namespace Input {

enum class AnalogDirection : u8 {
    RIGHT,
    LEFT,
    UP,
    DOWN,
};
struct AnalogProperties {
    float deadzone;
    float range;
    float threshold;
};
template <typename StatusType>
struct InputCallback {
    std::function<void(StatusType)> on_change;
};

/// An abstract class template for an input device (a button, an analog input, etc.).
template <typename StatusType>
class InputDevice {
public:
    virtual ~InputDevice() = default;
    virtual StatusType GetStatus() const {
        return {};
    }
    virtual StatusType GetRawStatus() const {
        return GetStatus();
    }
    virtual AnalogProperties GetAnalogProperties() const {
        return {};
    }
    virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
        return {};
    }
    virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
                               [[maybe_unused]] f32 amp_high,
                               [[maybe_unused]] f32 freq_high) const {
        return {};
    }
    void SetCallback(InputCallback<StatusType> callback_) {
        callback = std::move(callback_);
    }
    void TriggerOnChange() {
        if (callback.on_change) {
            callback.on_change(GetStatus());
        }
    }

private:
    InputCallback<StatusType> callback;
};

/// An abstract class template for a factory that can create input devices.
template <typename InputDeviceType>
class Factory {
public:
    virtual ~Factory() = default;
    virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
};

namespace Impl {

template <typename InputDeviceType>
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;

template <typename InputDeviceType>
struct FactoryList {
    static FactoryListType<InputDeviceType> list;
};

template <typename InputDeviceType>
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;

} // namespace Impl

/**
 * Registers an input device factory.
 * @tparam InputDeviceType the type of input devices the factory can create
 * @param name the name of the factory. Will be used to match the "engine" parameter when creating
 *     a device
 * @param factory the factory object to register
 */
template <typename InputDeviceType>
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
    auto pair = std::make_pair(name, std::move(factory));
    if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
        LOG_ERROR(Input, "Factory '{}' already registered", name);
    }
}

/**
 * Unregisters an input device factory.
 * @tparam InputDeviceType the type of input devices the factory can create
 * @param name the name of the factory to unregister
 */
template <typename InputDeviceType>
void UnregisterFactory(const std::string& name) {
    if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
        LOG_ERROR(Input, "Factory '{}' not registered", name);
    }
}

/**
 * Create an input device from given paramters.
 * @tparam InputDeviceType the type of input devices to create
 * @param params a serialized ParamPackage string contains all parameters for creating the device
 */
template <typename InputDeviceType>
std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
    const Common::ParamPackage package(params);
    const std::string engine = package.Get("engine", "null");
    const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
    const auto pair = factory_list.find(engine);
    if (pair == factory_list.end()) {
        if (engine != "null") {
            LOG_ERROR(Input, "Unknown engine name: {}", engine);
        }
        return std::make_unique<InputDeviceType>();
    }
    return pair->second->Create(package);
}

/**
 * A button device is an input device that returns bool as status.
 * true for pressed; false for released.
 */
using ButtonDevice = InputDevice<bool>;

/**
 * An analog device is an input device that returns a tuple of x and y coordinates as status. The
 * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
 * direction
 */
using AnalogDevice = InputDevice<std::tuple<float, float>>;

/**
 * A vibration device is an input device that returns an unsigned byte as status.
 * It represents whether the vibration device supports vibration or not.
 * If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
 */
using VibrationDevice = InputDevice<u8>;

/**
 * A motion status is an object that returns a tuple of accelerometer state vector,
 * gyroscope state vector, rotation state vector, orientation state matrix and quaterion state
 * vector.
 *
 * For both 3D vectors:
 *   x+ is the same direction as RIGHT on D-pad.
 *   y+ is normal to the touch screen, pointing outward.
 *   z+ is the same direction as UP on D-pad.
 *
 * For accelerometer state vector
 *   Units: g (gravitational acceleration)
 *
 * For gyroscope state vector:
 *   Orientation is determined by right-hand rule.
 *   Units: deg/sec
 *
 * For rotation state vector
 *   Units: rotations
 *
 * For orientation state matrix
 *   x vector
 *   y vector
 *   z vector
 *
 * For quaternion state vector
 *   xyz vector
 *   w float
 */
using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
                                std::array<Common::Vec3f, 3>, Common::Quaternion<f32>>;

/**
 * A motion device is an input device that returns a motion status object
 */
using MotionDevice = InputDevice<MotionStatus>;

/**
 * A touch status is an object that returns an array of 16 tuple elements of two floats and a bool.
 * The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is
 * pressed.
 */
using TouchStatus = std::array<std::tuple<float, float, bool>, 16>;

/**
 * A touch device is an input device that returns a touch status object
 */
using TouchDevice = InputDevice<TouchStatus>;

/**
 * A mouse device is an input device that returns a tuple of two floats and four ints.
 * The first two floats are X and Y device coordinates of the mouse (from 0-1).
 * The s32s are the mouse wheel.
 */
using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>;

} // namespace Input