QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
JoystickThumbPad.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3
4import QGroundControl
5import QGroundControl.Controls
6
7Item {
8 id: _joyRoot
9
10 property alias lightColors: mapPal.lightColors ///< true: use light colors from QGCMapPalette for drawing
11 property real xAxis: 0 ///< Value range [-1,1], negative values left stick, positive values right stick
12 property real yAxis: 0 ///< Value range [-1,1], negative values down stick, positive values up stick
13 property bool yAxisPositiveRangeOnly: false ///< true: value range [0,1], false: value range [-1,1]
14 property bool yAxisReCenter: false ///< true: snaps back to center on release, false: stays at current position on release
15 property real xPositionDelta: 0 ///< Amount to move the control on x axis
16 property real yPositionDelta: 0 ///< Amount to move the control on y axis
17 property bool touchActive: false ///< true: thumb is currently down on the pad
18
19 property real _centerXY: width / 2
20 property bool _processTouchPoints: false
21 property color _fgColor: QGroundControl.globalPalette.text
22 property color _bgColor: QGroundControl.globalPalette.window
23 property real _hatWidth: ScreenTools.defaultFontPixelHeight
24 property real _hatWidthHalf: _hatWidth / 2
25 property bool calculateYAxisMutex: true
26 property real stickPositionX: _centerXY
27 property real stickPositionY: !yAxisReCenter ? height : height / 2
28 property bool alreadyCreated: false
29
30 QGCMapPalette { id: mapPal }
31
32 onStickPositionXChanged: calculateXAxis()
33 onStickPositionYChanged: calculateYAxis()
34 onYAxisPositiveRangeOnlyChanged: calculateYAxis()
35 onYAxisReCenterChanged: yAxisReCentered()
36
37 function yAxisReCentered() {
38 if( yAxisReCenter ) {
39 yAxis = yAxisPositiveRangeOnly ? 0.5 : 0
40 stickPositionY = _joyRoot.height / 2
41 }
42 if( !alreadyCreated && !yAxisReCenter ) {
43 yAxis = yAxisPositiveRangeOnly ? 0 : -1
44 stickPositionY = _joyRoot.height
45 }
46 if ( alreadyCreated && !yAxisReCenter ){
47 yAxis = yAxisPositiveRangeOnly ? 0.5 : 0
48 stickPositionY = _joyRoot.height / 2
49 }
50 alreadyCreated = true
51 return yAxis
52 }
53
54 //We prevent Joystick to move while the screen is resizing
55 function resize( yPositionAfterResize ) {
56 if(_joyRoot.height <= 0) {
57 return;
58 }
59 calculateYAxisMutex = false
60 stickPositionY = ( 1 - ( ( yPositionAfterResize + ( !yAxisPositiveRangeOnly ? 1 : 0) ) / ( yAxisPositiveRangeOnly ? 1 : 2 ) )) * _joyRoot.height // Reverse the CalculateYAxis Procedure
61 stickPositionX = _joyRoot.width / 2 // Manual recenter
62 calculateYAxisMutex = true
63 }
64
65 function calculateXAxis() {
66 if(!_joyRoot.visible) {
67 return;
68 }
69 var xAxisTemp = stickPositionX / width
70 xAxisTemp *= 2.0
71 xAxisTemp -= 1.0
72 xAxis = xAxisTemp
73 }
74
75 function calculateYAxis() {
76 if(!_joyRoot.visible) {
77 return;
78 }
79 if(!calculateYAxisMutex) {
80 return;
81 }
82 var fullRange = yAxisPositiveRangeOnly ? 1 : 2
83 var pctUp = 1.0 - (stickPositionY / height)
84 var rangeUp = pctUp * fullRange
85 if (!yAxisPositiveRangeOnly) {
86 rangeUp -= 1
87 }
88 yAxis = rangeUp
89 }
90
91 function reCenter() {
92 _processTouchPoints = false
93 _centerXY = _joyRoot.width / 2 // Reload before using it to make sure of using the right value
94 // Move control back to original position
95 xPositionDelta = 0
96 yPositionDelta = 0
97
98 // Re-Center sticks as needed
99 stickPositionX = _centerXY
100 if (yAxisReCenter) {
101 stickPositionY = _centerXY
102 }
103 }
104
105 function thumbDown(touchPoints) {
106 // Position the control around the initial thumb position
107 _centerXY = _joyRoot.width / 2 // make sure to know the correct center of the item
108
109 var limitOffset = uiRealX >= _joyRoot.width / 2 ? true : false // as the joystick become small the UI too so we limit the maxOffset for reCentering joystick to prevent misclicks
110 var maxDelta = _joyRoot.x > uiTotalWidth / 2 ? uiTotalWidth - uiRealX - _joyRoot.x - _centerXY : uiRealX
111 var isRightJoystick = _joyRoot.x > uiTotalWidth / 2 ? true : false
112
113 // Check if new xDelta will make joystick to be beyond screen boundaries or can cause a misclick
114 if (!limitOffset && isRightJoystick && touchPoints[0].x <= maxDelta || !limitOffset && !isRightJoystick && touchPoints[0].x >= maxDelta) {
115 xPositionDelta = touchPoints[0].x - _centerXY
116 } else if (limitOffset && !isRightJoystick && touchPoints[0].x >= _centerXY * 0.25 && touchPoints[0].x <= _centerXY * 2) { // more offset at the side near to the center
117 xPositionDelta = touchPoints[0].x - _centerXY
118 } else if (limitOffset && isRightJoystick && touchPoints[0].x >= 0 && touchPoints[0].x <= _centerXY * 1.75) {
119 xPositionDelta = touchPoints[0].x - _centerXY
120 } else {
121 return;
122 }
123
124 if (yAxisPositiveRangeOnly) {
125 yPositionDelta = touchPoints[0].y - stickPositionY
126 } else {
127 yPositionDelta = touchPoints[0].y - _centerXY
128 }
129 // We need to wait until we move the control to the right position before we process touch points
130 _processTouchPoints = true
131 }
132
133 /*
134 // Keep in for debugging
135 Column {
136 QGCLabel { text: xAxis }
137 QGCLabel { text: yAxis }
138 }
139 */
140
141 Image {
142 anchors.fill: parent
143 source: "/res/JoystickBezelLight.png"
144 mipmap: true
145 smooth: true
146 }
147
148 Rectangle {
149 anchors.fill: parent
150 radius: width / 2
151 color: _bgColor
152 opacity: 0.5
153
154 Rectangle {
155 anchors.margins: parent.width / 4
156 anchors.fill: parent
157 radius: width / 2
158 border.color: _fgColor
159 border.width: 2
160 color: "transparent"
161 }
162
163 Rectangle {
164 anchors.fill: parent
165 radius: width / 2
166 border.color: _fgColor
167 border.width: 2
168 color: "transparent"
169 }
170 }
171
172 QGCColoredImage {
173 color: _fgColor
174 visible: yAxisPositiveRangeOnly
175 height: ScreenTools.defaultFontPixelHeight
176 width: height
177 sourceSize.height: height
178 mipmap: true
179 fillMode: Image.PreserveAspectFit
180 source: "/res/clockwise-arrow.svg"
181 anchors.right: parent.right
182 anchors.rightMargin: ScreenTools.defaultFontPixelWidth
183 anchors.verticalCenter: parent.verticalCenter
184 }
185
186 QGCColoredImage {
187 color: _fgColor
188 visible: yAxisPositiveRangeOnly
189 height: ScreenTools.defaultFontPixelHeight
190 width: height
191 sourceSize.height: height
192 mipmap: true
193 fillMode: Image.PreserveAspectFit
194 source: "/res/counter-clockwise-arrow.svg"
195 anchors.left: parent.left
196 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
197 anchors.verticalCenter: parent.verticalCenter
198 }
199
200 QGCColoredImage {
201 color: _fgColor
202 visible: yAxisPositiveRangeOnly
203 height: ScreenTools.defaultFontPixelHeight
204 width: height
205 sourceSize.height: height
206 mipmap: true
207 fillMode: Image.PreserveAspectFit
208 source: "/res/chevron-up.svg"
209 anchors.top: parent.top
210 anchors.topMargin: ScreenTools.defaultFontPixelWidth
211 anchors.horizontalCenter: parent.horizontalCenter
212 }
213
214 QGCColoredImage {
215 color: _fgColor
216 visible: yAxisPositiveRangeOnly
217 height: ScreenTools.defaultFontPixelHeight
218 width: height
219 sourceSize.height: height
220 mipmap: true
221 fillMode: Image.PreserveAspectFit
222 source: "/res/chevron-down.svg"
223 anchors.bottom: parent.bottom
224 anchors.bottomMargin: ScreenTools.defaultFontPixelWidth
225 anchors.horizontalCenter: parent.horizontalCenter
226 }
227
228 Rectangle {
229 width: _hatWidth
230 height: _hatWidth
231 radius: _hatWidthHalf
232 border.color: _fgColor
233 border.width: 1
234 color: Qt.rgba(_fgColor.r, _fgColor.g, _fgColor.b, 0.5)
235 x: stickPositionX - _hatWidthHalf
236 y: stickPositionY - _hatWidthHalf
237 }
238
239 Connections {
240 target: touchPoint
241
242 onXChanged: {
243 if (_processTouchPoints) {
244 _joyRoot.stickPositionX = Math.max(Math.min(touchPoint.x, _joyRoot.width), 0)
245 }
246 }
247 onYChanged: {
248 if (_processTouchPoints) {
249 _joyRoot.stickPositionY = Math.max(Math.min(touchPoint.y, _joyRoot.height), 0)
250 }
251 }
252 }
253
254 MultiPointTouchArea {
255 anchors.fill: parent
256 anchors.bottomMargin: yAxisReCenter ? 0 : -_hatWidthHalf
257 minimumTouchPoints: 1
258 maximumTouchPoints: 1
259 touchPoints: [ TouchPoint { id: touchPoint } ]
260 onPressed: touchPoints => {
261 _joyRoot.touchActive = true
262 _joyRoot.thumbDown(touchPoints)
263 }
264 onReleased: {
265 _joyRoot.touchActive = false
266 _joyRoot.reCenter()
267 }
268 onCanceled: {
269 _joyRoot.touchActive = false
270 _joyRoot.reCenter()
271 }
272 }
273}