1 | // Copyright (C) 2024 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dxractionmapper_p.h" |
5 | |
6 | QT_BEGIN_NAMESPACE |
7 | |
8 | QQuick3DXrActionMapper::QQuick3DXrActionMapper(QObject *parent) : QObject(parent) |
9 | { |
10 | } |
11 | |
12 | static inline quint32 actionIntKey(const QQuick3DXrInputAction::Action id, const QQuick3DXrInputAction::Hand hand) |
13 | { |
14 | return quint16(id) | (quint32(hand) << 16); |
15 | } |
16 | |
17 | static inline QString actionStringKey(const QString &name, const QQuick3DXrInputAction::Hand hand) |
18 | { |
19 | return QString::number(hand) + name; |
20 | } |
21 | |
22 | QQuick3DXrActionMapper *QQuick3DXrActionMapper::instance() |
23 | { |
24 | static QQuick3DXrActionMapper instance; |
25 | return &instance; |
26 | } |
27 | |
28 | void QQuick3DXrActionMapper::handleInput(QQuick3DXrInputAction::Action id, QQuick3DXrInputAction::Hand hand, const char *shortName, float value) |
29 | { |
30 | auto *that = instance(); |
31 | auto set = [](auto action, auto value) { |
32 | action->setValue(value); |
33 | // TODO: distinguish between bool and float values |
34 | action->setPressed(value > 0.9); |
35 | }; |
36 | |
37 | const QLatin1StringView name(shortName); |
38 | // emit that->inputValueChange(id, name, value); // TODO: emit a signal from public class |
39 | |
40 | QList<QQuick3DXrInputAction *> actions; |
41 | if (id == QQuick3DXrInputAction::CustomAction) { |
42 | actions = that->m_customActions.values(key: actionStringKey(name, hand)); |
43 | } else { |
44 | actions = that->m_actions.values(key: actionIntKey(id, hand)); |
45 | } |
46 | |
47 | for (const auto &action : std::as_const(t&: actions)) |
48 | set(action, value); |
49 | } |
50 | |
51 | // Note: it is the responsibility of the caller to call removeAction() before the action is destroyed or actionId/actionName is changed |
52 | void QQuick3DXrActionMapper::registerAction(QQuick3DXrInputAction *action) |
53 | { |
54 | auto *that = instance(); |
55 | |
56 | const auto &idList = action->actionId(); |
57 | const auto hand = action->hand(); |
58 | |
59 | if (idList.isEmpty()) { |
60 | that->m_customActions.insert(key: actionStringKey(name: action->actionName(), hand), value: action); |
61 | } else { |
62 | for (const auto &id : idList) { |
63 | if (id != QQuick3DXrInputAction::CustomAction) |
64 | that->m_actions.insert(key: actionIntKey(id, hand), value: action); |
65 | } |
66 | } |
67 | } |
68 | |
69 | void QQuick3DXrActionMapper::removeAction(QQuick3DXrInputAction *action) |
70 | { |
71 | auto *that = instance(); |
72 | |
73 | const auto idList = action->actionId(); |
74 | const auto hand = action->hand(); |
75 | if (idList.isEmpty()) { |
76 | that->m_customActions.remove(key: action->actionName(), value: action); |
77 | } else { |
78 | for (const auto &id : idList) { |
79 | if (id != QQuick3DXrInputAction::CustomAction) |
80 | that->m_actions.remove(key: actionIntKey(id, hand)); |
81 | } |
82 | } |
83 | } |
84 | |
85 | /*! |
86 | \qmltype XrInputAction |
87 | \inherits QtObject |
88 | \inqmlmodule QtQuick3D.Xr |
89 | \brief Represents an action from an input controller. |
90 | |
91 | Actions can be boolean, such as a button press, or analog, such as a joystick axis. |
92 | |
93 | Use the \l pressed property or the \l triggered signal to react to a boolean action. An analog action will set the \l value property. |
94 | |
95 | \note For convenience, an analog property will also set the \c pressed property and emit the \c triggered signal, |
96 | while a boolean property will set \c value to 1.0 when pressed. |
97 | |
98 | The following shows how to react to either the right hand grip being pressed or to a right hand pinch gesture from hand tracking: |
99 | |
100 | \qml |
101 | XrInputAction { |
102 | hand: XrInputAction.RightHand |
103 | actionId: [XrInputAction.SqueezePressed, XrInputAction.SqueezeValue, XrInputAction.IndexFingerPinch] |
104 | onTriggered: console.log("Do action here.") |
105 | } |
106 | \endqml |
107 | |
108 | The reason for specifying both \c SqueezePressed and \c SqueezeValue is that some controllers have an analog grip button, |
109 | and some controllers just have an on/off grip switch. |
110 | */ |
111 | |
112 | /*! |
113 | \qmlsignal XrInputAction::triggered() |
114 | |
115 | This signal is emitted when a boolean action is activated. This happens at the same time as the \l pressed property is set to \c true. |
116 | */ |
117 | |
118 | void QQuick3DXrInputAction::setValue(float newValue) |
119 | { |
120 | if (qFuzzyCompare(p1: m_value, p2: newValue)) |
121 | return; |
122 | m_value = newValue; |
123 | emit valueChanged(); |
124 | } |
125 | |
126 | /*! |
127 | \qmlproperty bool XrInputAction::pressed |
128 | \brief Indicates whether the input action is currently pressed. |
129 | |
130 | Use this property to check if the input action (for example, a button) |
131 | is currently pressed. |
132 | */ |
133 | |
134 | bool QQuick3DXrInputAction::pressed() const |
135 | { |
136 | return m_pressed; |
137 | } |
138 | |
139 | void QQuick3DXrInputAction::setPressed(bool newPressed) |
140 | { |
141 | if (m_pressed == newPressed) |
142 | return; |
143 | m_pressed = newPressed; |
144 | emit pressedChanged(); |
145 | if (newPressed) |
146 | emit triggered(); |
147 | } |
148 | |
149 | /*! |
150 | \qmlproperty string XrInputAction::actionName |
151 | \brief The name of the input action. |
152 | |
153 | Use this property to specify the name of the custom input action you want to react to. This property does not have an effect if \l actionId is set. |
154 | */ |
155 | |
156 | QString QQuick3DXrInputAction::actionName() const |
157 | { |
158 | return m_actionName; |
159 | } |
160 | |
161 | void QQuick3DXrInputAction::setActionName(const QString &newActionName) |
162 | { |
163 | if (m_actionName == newActionName) |
164 | return; |
165 | const bool needsRemap = m_actionIds.isEmpty() && m_componentComplete; |
166 | if (needsRemap) |
167 | QQuick3DXrActionMapper::removeAction(action: this); |
168 | m_actionName = newActionName; |
169 | if (needsRemap) |
170 | QQuick3DXrActionMapper::registerAction(action: this); |
171 | emit actionNameChanged(); |
172 | } |
173 | |
174 | QQuick3DXrInputAction::QQuick3DXrInputAction(QObject *parent) |
175 | : QObject(parent) |
176 | { |
177 | } |
178 | |
179 | QQuick3DXrInputAction::~QQuick3DXrInputAction() |
180 | { |
181 | QQuick3DXrActionMapper::removeAction(action: this); |
182 | } |
183 | |
184 | /*! |
185 | \qmlproperty float XrInputAction::value |
186 | \brief The analog value of the input action. |
187 | |
188 | For analog inputs, such as a thumbstick position, this property holds |
189 | the value of the input (usually in the range [0, 1]). |
190 | */ |
191 | |
192 | float QQuick3DXrInputAction::value() const |
193 | { |
194 | return m_value; |
195 | } |
196 | |
197 | void QQuick3DXrInputAction::classBegin() |
198 | { |
199 | } |
200 | |
201 | void QQuick3DXrInputAction::componentComplete() |
202 | { |
203 | QQuick3DXrActionMapper::registerAction(action: this); |
204 | m_componentComplete = true; |
205 | } |
206 | |
207 | /*! |
208 | \qmlproperty List<enumeration> XrInputAction::actionId |
209 | \brief Specifies the action(s) to react to |
210 | |
211 | Holds a List of IDs that can be of the following values: |
212 | |
213 | \value XrInputAction.Button1Pressed Button 1 is pressed. \e Boolean. |
214 | \value XrInputAction.Button1Touched Button 1 is touched. \e Boolean. |
215 | \value XrInputAction.Button2Pressed Button 2 is pressed. \e Boolean. |
216 | \value XrInputAction.Button2Touched Button 2 is touched. \e Boolean. |
217 | \value XrInputAction.ButtonMenuPressed The menu button is pressed. \e Boolean. |
218 | \value XrInputAction.ButtonMenuTouched The menu button is touched. \e Boolean. |
219 | \value XrInputAction.ButtonSystemPressed The system button is pressed. \e Boolean. |
220 | \value XrInputAction.ButtonSystemTouched The system button is touched. \e Boolean. |
221 | \value XrInputAction.SqueezeValue How far the grip button is pressed. \e Analog. |
222 | \value XrInputAction.SqueezeForce The force applied to the grip button. \e Analog. |
223 | \value XrInputAction.SqueezePressed The grip button is pressed. \e Boolean. |
224 | \value XrInputAction.TriggerValue How far the trigger button is pressed. \e Analog. |
225 | \value XrInputAction.TriggerPressed The trigger is pressed. \e Boolean. |
226 | \value XrInputAction.TriggerTouched The trigger is touched. \e Boolean. |
227 | \value XrInputAction.ThumbstickX The X-axis value of the thumbstick. \e Analog. |
228 | \value XrInputAction.ThumbstickY The Y-axis value of the thumbstick. \e Analog. |
229 | \value XrInputAction.ThumbstickPressed The thumbstick is pressed. \e Boolean. |
230 | \value XrInputAction.ThumbstickTouched The thumbstick is touched. \e Boolean. |
231 | \value XrInputAction.ThumbrestTouched The thumbrest is touched. \e Boolean. |
232 | \value XrInputAction.TrackpadX The X-axis position on the trackpad. \e Analog. |
233 | \value XrInputAction.TrackpadY The Y-axis position on the trackpad. \e Analog. |
234 | \value XrInputAction.TrackpadForce The force applied on the trackpad. \e Analog. |
235 | \value XrInputAction.TrackpadTouched The trackpad is touched. \e Boolean. |
236 | \value XrInputAction.TrackpadPressed The trackpad is pressed. \e Boolean. |
237 | \value XrInputAction.IndexFingerPinch Thumb to index finger pinch gesture. \e Boolean. |
238 | \value XrInputAction.MiddleFingerPinch Thumb to middle finger pinch gesture. \e Boolean. |
239 | \value XrInputAction.RingFingerPinch Thumb to ring finger pinch gesture. \e Boolean. |
240 | \value XrInputAction.LittleFingerPinch Thumb to little finger pinch gesture. \e Boolean. |
241 | \value XrInputAction.HandTrackingMenuPress Hand tracking menu gesture. \e Boolean. |
242 | */ |
243 | |
244 | QList<QQuick3DXrInputAction::Action> QQuick3DXrInputAction::actionId() const |
245 | { |
246 | return m_actionIds; |
247 | } |
248 | |
249 | void QQuick3DXrInputAction::setActionId(const QList<Action> &newActionId) |
250 | { |
251 | if (m_actionIds == newActionId) |
252 | return; |
253 | |
254 | if (m_componentComplete) |
255 | QQuick3DXrActionMapper::removeAction(action: this); |
256 | |
257 | m_actionIds = newActionId; |
258 | |
259 | if (m_componentComplete) |
260 | QQuick3DXrActionMapper::registerAction(action: this); |
261 | |
262 | emit actionIdChanged(); |
263 | } |
264 | |
265 | /*! |
266 | \qmlproperty enumeration QtQuick3D.Xr::XrInputAction::hand |
267 | \brief The Hand that this input action will apply to. |
268 | |
269 | Specifies the hand ro react to. |
270 | |
271 | It can be one of: |
272 | |
273 | \value XrInputAction.LeftHand |
274 | \value XrInputAction.RightHand |
275 | \value XrInputAction.Unknown |
276 | */ |
277 | |
278 | QQuick3DXrInputAction::Hand QQuick3DXrInputAction::hand() const |
279 | { |
280 | return m_hand; |
281 | } |
282 | |
283 | void QQuick3DXrInputAction::setHand(Hand newHand) |
284 | { |
285 | if (m_hand == newHand) |
286 | return; |
287 | m_hand = newHand; |
288 | emit handChanged(); |
289 | } |
290 | |
291 | QT_END_NAMESPACE |
292 | |