QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
ValueSlider.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4import Qt.labs.animation
5
6import QGroundControl
7import QGroundControl.Controls
8
9Control {
10 id: control
11
12 property real value: 0
13 property real from: 0
14 property real to: 100
15 property string unitsString
16 property string label
17
18 required property int decimalPlaces
19 required property real majorTickStepSize
20
21 property int _tickValueDecimalPlaces: _countDecimalPlaces(majorTickStepSize)
22
23 property real _indicatorCenterPos: width / 2
24
25 property real _majorTickSpacing: ScreenTools.defaultFontPixelWidth * 6
26
27 property real _majorTickSize: valueIndicator.pointerSize + valueIndicator.indicatorValueMargins
28 property real _tickValueEdgeMargin: ScreenTools.defaultFontPixelWidth / 2
29 property real _minorTickSize: _majorTickSize / 2
30 property real _sliderValuePerPixel: majorTickStepSize / _majorTickSpacing
31
32 property int _minorTickValueStep: majorTickStepSize / 2
33
34 //property real _sliderValue: _firstPixelValue + ((sliderFlickable.contentX + _indicatorCenterPos) * _sliderValuePerPixel)
35
36 // Calculate the full range of the slider. We have been given a min/max but that is for clamping the selected slider values.
37 // We need expand that range to take into account additional values that must be displayed above/below the value indicator
38 // when it is at min/max.
39
40 // Add additional major ticks above/below min/max to ensure we can display the full visual range of the slider
41 property int _majorTicksVisibleBeyondIndicator: Math.ceil(_indicatorCenterPos / _majorTickSpacing)
42 property real _majorTicksExtentsAdjustment: _majorTicksVisibleBeyondIndicator * majorTickStepSize
43
44 // Calculate the min/max for the full slider range
45 property real _majorTickMinValue: Math.floor((from - _majorTicksExtentsAdjustment) / majorTickStepSize) * majorTickStepSize
46 property real _majorTickMaxValue: Math.floor((to + _majorTicksExtentsAdjustment) / majorTickStepSize) * majorTickStepSize
47
48 // Now calculate the position we draw the first tick mark such that we are not allowed to flick above the max value
49 property real _firstTickPixelOffset: _indicatorCenterPos - ((from - _majorTickMinValue) / _sliderValuePerPixel)
50 property real _firstPixelValue: _majorTickMinValue - (_firstTickPixelOffset * _sliderValuePerPixel)
51
52 property int _cMajorTicks: (_majorTickMaxValue - _majorTickMinValue) / majorTickStepSize + 1
53
54 // Calculate the slider width such that we can flick through the full range of the slider
55 property real _sliderContentSize: ((to - _firstPixelValue) / _sliderValuePerPixel) + (background.width - _indicatorCenterPos)
56
57 property bool _loadComplete: false
58
59 property var qgcPal: QGroundControl.globalPalette
60
61 function setValue(value) {
62 value = _clampedSliderValue(value)
63 if (value !== control.value) {
64 control.value = value
65 _recalcSliderPos(false)
66 }
67 }
68
69 function _outputInternalValues() {
70 console.log("ValueSlider: width", width)
71 console.log("ValueSlider: _indicatorCenterPos", _indicatorCenterPos)
72 console.log("ValueSlider: _firstPixelValue", _firstPixelValue)
73 console.log("ValueSlider: _firstTickPixelOffset", _firstTickPixelOffset)
74 console.log("ValueSlider: _sliderValuePerPixel", _sliderValuePerPixel)
75 console.log("ValueSlider: _indicatorCenterPos", _indicatorCenterPos)
76 console.log("ValueSlider: _majorTickSpacing", _majorTickSpacing)
77 console.log("ValueSlider: _majorTickSize", _majorTickSize)
78 console.log("ValueSlider: _minorTickSize", _minorTickSize)
79 console.log("ValueSlider: _majorTicksVisibleBeyondIndicator", _majorTicksVisibleBeyondIndicator)
80 console.log("ValueSlider: _majorTicksExtentsAdjustment", _majorTicksExtentsAdjustment)
81 console.log("ValueSlider: _majorTickMinValue", _majorTickMinValue)
82 console.log("ValueSlider: _majorTickMaxValue", _majorTickMaxValue)
83 console.log("ValueSlider: _cMajorTicks", _cMajorTicks)
84 console.log("ValueSlider: _sliderContentSize", _sliderContentSize)
85 }
86
87 Component.onCompleted: {
88 _recalcSliderPos(false)
89 //_outputInternalValues()
90 _loadComplete = true
91 }
92
93 onWidthChanged: {
94 if (_loadComplete) {
95 _recalcSliderPos()
96 //_outputInternalValues()
97 }
98 }
99
100 function _countDecimalPlaces(number) {
101 const numberString = number.toString()
102 if (numberString.includes('.')) {
103 return numberString.split('.')[1].length
104 } else {
105 return 0
106 }
107 }
108
109 function _sliderXPosToValue(xPos) {
110 return _firstPixelValue + ((-xPos + _indicatorCenterPos) * _sliderValuePerPixel)
111 }
112
113 function _valueToSliderXPos(value) {
114 return -((value - _firstPixelValue) / _sliderValuePerPixel) + _indicatorCenterPos
115 }
116
117 function _recalcSliderPos(animate = true) {
118 // Position the slider such that the indicator is pointing to the current value
119 let sliderXPos = _valueToSliderXPos(value)
120 if (animate) {
121 flickableAnimation.from = sliderContainer.x
122 flickableAnimation.to = sliderXPos
123 flickableAnimation.start()
124 } else {
125 sliderContainer.x = sliderXPos
126 }
127 }
128
129 function _clampedSliderValue(value) {
130 return Math.min(Math.max(value, from), to).toFixed(decimalPlaces)
131 }
132
133 QGCPalette {
134 id: qgcPal
135 colorGroupEnabled: control.enabled
136 }
137
138 // This TapHandler ensures that the slider captures touch and click events,
139 // preventing them from passing through to the underlying map.
140 TapHandler {
141 acceptedButtons: Qt.AllButtons
142 onTapped: control.forceActiveFocus()
143 grabPermissions: PointerHandler.CanTakeOverFromAnything
144 }
145
146 background: Item {
147 implicitHeight: _majorTickSize + tickValueMargin + ScreenTools.defaultFontPixelHeight + labelOffset
148 clip: true
149
150 property real tickValueMargin: ScreenTools.defaultFontPixelHeight / 3
151 property real labelOffset: labelItem.visible ? labelItem.contentHeight / 2 : 0
152
153 Item {
154 id: sliderContainer
155 y: background.labelOffset
156 width: _sliderContentSize
157 height: background.height - y
158
159 onXChanged: {
160 if (dragHandler.active) {
161 value = _sliderXPosToValue(x)
162 }
163 }
164
165 DragHandler {
166 id: dragHandler
167 yAxis.enabled: false
168 }
169
170 BoundaryRule on x {
171 minimum: _valueToSliderXPos(to)
172 maximum: 0
173 }
174
175 PropertyAnimation on x {
176 id: flickableAnimation
177 duration: 500
178 easing.type: Easing.OutCubic
179 running: false
180 }
181
182 // Major tick marks
183 Repeater {
184 model: _cMajorTicks
185
186 Item {
187 width: 1
188 height: sliderContainer.height
189 x: _majorTickSpacing * index + _firstTickPixelOffset
190 opacity: tickValue < from || tickValue > to ? 0.5 : 1
191
192 property real tickValue: _majorTickMinValue + (majorTickStepSize * index)
193
194 Rectangle {
195 id: majorTickMark
196 width: 1
197 height: _majorTickSize
198 color: qgcPal.text
199 }
200
201 QGCLabel {
202 anchors.bottomMargin: _tickValueEdgeMargin
203 anchors.bottom: parent.bottom
204 anchors.horizontalCenter: majorTickMark.horizontalCenter
205 text: parent.tickValue.toFixed(_tickValueDecimalPlaces)
206 }
207 }
208 }
209
210 // Minor tick marks
211 Repeater {
212 model: _cMajorTicks * 2
213
214 Rectangle {
215 x: _majorTickSpacing / 2 * index + + _firstTickPixelOffset
216 width: 1
217 height: _minorTickSize
218 color: qgcPal.text
219 opacity: tickValue < from || tickValue > to ? 0.5 : 1
220 visible: index % 2 === 1
221
222 property real tickValue: _majorTickMaxValue - ((majorTickStepSize / 2) * index)
223 }
224 }
225 }
226
227 Rectangle {
228 id: labelItemBackground
229 width: labelItem.contentWidth
230 height: labelItem.contentHeight
231 color: qgcPal.window
232 opacity: 0.8
233 visible: labelItem.visible
234 }
235
236 QGCLabel {
237 id: labelItem
238 anchors.left: labelItemBackground.left
239 anchors.top: labelItemBackground.top
240 text: label
241 visible: label !== ""
242 }
243 }
244
245 contentItem: Item {
246 implicitHeight: valueIndicator.height
247
248 Canvas {
249 id: valueIndicator
250 anchors.bottom: parent.bottom
251 anchors.horizontalCenter: parent.horizontalCenter
252 width: Math.max(valueLabel.contentWidth + (indicatorValueMargins * 2), pointerSize * 2 + 2)
253 height: valueLabel.contentHeight + (indicatorValueMargins * 2) + pointerSize
254
255 property real indicatorValueMargins: ScreenTools.defaultFontPixelWidth / 2
256 property real indicatorHeight: valueLabel.contentHeight
257 property real pointerSize: ScreenTools.defaultFontPixelWidth
258
259 onPaint: {
260 var ctx = getContext("2d")
261 ctx.strokeStyle = qgcPal.text
262 ctx.fillStyle = qgcPal.window
263 ctx.lineWidth = 1
264 ctx.beginPath()
265 ctx.moveTo(width / 2, 0)
266 ctx.lineTo(width / 2 + pointerSize, pointerSize)
267 ctx.lineTo(width - 1, pointerSize)
268 ctx.lineTo(width - 1, height - 1)
269 ctx.lineTo(1, height - 1)
270 ctx.lineTo(1, pointerSize)
271 ctx.lineTo(width / 2 - pointerSize, pointerSize)
272 ctx.closePath()
273 ctx.fill()
274 ctx.stroke()
275 }
276
277 QGCLabel {
278 id: valueLabel
279 anchors.bottomMargin: parent.indicatorValueMargins
280 anchors.bottom: parent.bottom
281 anchors.horizontalCenter: parent.horizontalCenter
282 horizontalAlignment: Text.AlignHCenter
283 verticalAlignment: Text.AlignBottom
284 text: _clampedSliderValue(value) + (unitsString !== "" ? " " + unitsString : "")
285 }
286
287 QGCMouseArea {
288 anchors.fill: parent
289 onClicked: {
290 sliderValueTextField.text = _clampedSliderValue(value)
291 sliderValueTextField.visible = true
292 sliderValueTextField.forceActiveFocus()
293 }
294 }
295
296 QGCTextField {
297 id: sliderValueTextField
298 anchors.topMargin: valueIndicator.pointerSize
299 anchors.fill: parent
300 showUnits: true
301 unitsLabel: unitsString
302 visible: false
303 numericValuesOnly: true
304
305 onEditingFinished: {
306 visible = false
307 focus = false
308 value = _clampedSliderValue(parseFloat(text))
309 _recalcSliderPos()
310 }
311
312 Connections {
313 target: control
314 function onValueChanged() { sliderValueTextField.visible = false }
315 }
316 }
317 }
318 }
319}