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
6QT_BEGIN_NAMESPACE
7
8QQuick3DXrActionMapper::QQuick3DXrActionMapper(QObject *parent) : QObject(parent)
9{
10}
11
12static inline quint32 actionIntKey(const QQuick3DXrInputAction::Action id, const QQuick3DXrInputAction::Hand hand)
13{
14 return quint16(id) | (quint32(hand) << 16);
15}
16
17static inline QString actionStringKey(const QString &name, const QQuick3DXrInputAction::Hand hand)
18{
19 return QString::number(hand) + name;
20}
21
22QQuick3DXrActionMapper *QQuick3DXrActionMapper::instance()
23{
24 static QQuick3DXrActionMapper instance;
25 return &instance;
26}
27
28void 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
52void 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
69void 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
118void 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
134bool QQuick3DXrInputAction::pressed() const
135{
136 return m_pressed;
137}
138
139void 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
156QString QQuick3DXrInputAction::actionName() const
157{
158 return m_actionName;
159}
160
161void 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
174QQuick3DXrInputAction::QQuick3DXrInputAction(QObject *parent)
175 : QObject(parent)
176{
177}
178
179QQuick3DXrInputAction::~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
192float QQuick3DXrInputAction::value() const
193{
194 return m_value;
195}
196
197void QQuick3DXrInputAction::classBegin()
198{
199}
200
201void 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
244QList<QQuick3DXrInputAction::Action> QQuick3DXrInputAction::actionId() const
245{
246 return m_actionIds;
247}
248
249void 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
278QQuick3DXrInputAction::Hand QQuick3DXrInputAction::hand() const
279{
280 return m_hand;
281}
282
283void QQuick3DXrInputAction::setHand(Hand newHand)
284{
285 if (m_hand == newHand)
286 return;
287 m_hand = newHand;
288 emit handChanged();
289}
290
291QT_END_NAMESPACE
292

source code of qtquick3d/src/xr/quick3dxr/qquick3dxractionmapper.cpp