QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GuidedValueSlider.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4
5import QGroundControl
6import QGroundControl.Controls
7
8Item {
9 id: control
10 width: indicatorCanvas.width
11 clip: true
12
13 enum SliderType {
14 Altitude,
15 Takeoff,
16 Speed
17 }
18
19 property real _sliderMaxVal: 0
20 property real _sliderMinVal: 0
21 property int _sliderType: GuidedValueSlider.SliderType.Altitude
22 property string _displayText: ""
23
24 property var _unitsSettings: QGroundControl.settingsManager.unitsSettings
25 property real _indicatorCenterPos: sliderFlickable.height / 2
26
27 property int _fullSliderRangeIndex: 2
28 property var _rgValueRanges: [ 400, 200, 100, 50, 25, 10 ]
29 property var _rgValidMajorTickSteps: [ 10, 25, 50, 100 ]
30 property int _fullSliderValueRange: _rgValueRanges[_fullSliderRangeIndex]
31 property int _halfSliderValueRange: _fullSliderValueRange / 2
32
33 property real _majorTickWidth: ScreenTools.largeFontPixelWidth * 2
34 property real _majorTickPixelHeight: ScreenTools.largeFontPixelHeight * 2
35 property real _tickValueRightMargin: ScreenTools.defaultFontPixelWidth / 2
36 property real _minorTickWidth: _majorTickWidth / 2
37 property real _sliderValuePerPixel: _majorTickValueStep / _majorTickPixelHeight
38
39 property int _majorTickValueStep: 10
40 property int _minorTickValueStep: _majorTickValueStep / 2
41
42 property real _sliderValue: _firstPixelValue - ((sliderFlickable.contentY + _indicatorCenterPos) * _sliderValuePerPixel)
43
44 // Calculate the full range of the slider. We have been given a min/max but that is for clamping the selected slider values.
45 // We need expand that range to take into account additional values that must be displayed above/below the value indicator
46 // when it is at min/max.
47
48 // Add additional major ticks above/below min/max to ensure we can display the full visual range of the slider
49 property int _majorTicksVisibleAboveIndicator: Math.floor(_indicatorCenterPos / _majorTickPixelHeight)
50 property int _majorTickAdjustment: _majorTicksVisibleAboveIndicator * _majorTickValueStep
51
52 // Calculate the next major tick above/below min/max
53 property int _majorTickMaxValue: Math.ceil((_sliderMaxVal + _majorTickAdjustment)/ _majorTickValueStep) * _majorTickValueStep
54 property int _majorTickMinValue: Math.floor((_sliderMinVal - _majorTickAdjustment)/ _majorTickValueStep) * _majorTickValueStep
55
56 // Now calculate the position we draw the first tick mark such that we are not allowed to flick above the max value
57 property real _firstTickPixelOffset: _indicatorCenterPos - ((_majorTickMaxValue - _sliderMaxVal) / _sliderValuePerPixel)
58 property real _firstPixelValue: _majorTickMaxValue + (_firstTickPixelOffset * _sliderValuePerPixel)
59
60 // Calculate the slider height such that we can flick below the min value
61 property real _sliderHeight: (_firstPixelValue - _sliderMinVal) / _sliderValuePerPixel + (sliderFlickable.height - _indicatorCenterPos)
62
63 property int _cMajorTicks: (_majorTickMaxValue - _majorTickMinValue) / _majorTickValueStep + 1
64
65 property var _qgcPal: QGroundControl.globalPalette
66
67 function setCurrentValue(currentValue, animate = true) {
68 // Position the slider such that the indicator is pointing to the current value
69 var contentY = (_firstPixelValue - currentValue) / _sliderValuePerPixel - _indicatorCenterPos
70 if (animate) {
71 flickableAnimation.from = sliderFlickable.contentY
72 flickableAnimation.to = contentY
73 flickableAnimation.start()
74 } else {
75 sliderFlickable.contentY = contentY
76 }
77 }
78
79 /// Slider values should be in converted app units.
80 function setupSlider(sliderType, minValue, maxValue, currentValue, displayText) {
81 //console.log("setupSlider: sliderType: ", sliderType, " minValue: ", minValue, " maxValue: ", maxValue, " currentValue: ", currentValue, " displayText: ", displayText)
82 _sliderType = sliderType
83 _sliderMinVal = minValue
84 _sliderMaxVal = maxValue
85 _displayText = displayText
86 setCurrentValue(currentValue, false)
87 }
88
89 function _clampedSliderValueString(value) {
90 var decimalPlaces = 0
91 if (_unitsSettings.verticalDistanceUnits.rawValue === UnitsSettings.VerticalDistanceUnitsMeters) {
92 decimalPlaces = 1
93 }
94 return Math.min(Math.max(value , _sliderMinVal), _sliderMaxVal).toFixed(decimalPlaces)
95 }
96
97 function getOutputValue() {
98 return parseFloat(_clampedSliderValueString(_sliderValue))
99 }
100
101 DeadMouseArea {
102 anchors.fill: parent
103 }
104
105 Rectangle {
106 anchors.fill: parent
107 color: _qgcPal.window
108 opacity: 0.5
109 }
110
111 ColumnLayout {
112 anchors.fill: parent
113 spacing: 0
114
115 QGCLabel {
116 Layout.fillWidth: true
117 horizontalAlignment: Text.AlignHCenter
118 font.pointSize: ScreenTools.smallFontPointSize
119 text: _displayText
120 }
121
122 QGCFlickable {
123 id: sliderFlickable
124 Layout.fillWidth: true
125 Layout.fillHeight: true
126 contentWidth: sliderContainer.width
127 contentHeight: sliderContainer.height
128 flickableDirection: Flickable.VerticalFlick
129
130 // The default deceleration is too fast for the slider. We need to slow it down a bit. This allows for large movements of the slider
131 // to set large altitudes.
132 Component.onCompleted: flickDeceleration = flickDeceleration / 2
133
134 PropertyAnimation on contentY {
135 id: flickableAnimation
136 duration: 500
137 from: fromValue
138 to: toValue
139 easing.type: Easing.OutCubic
140 running: false
141
142 property real fromValue
143 property real toValue
144 }
145
146 Item {
147 id: sliderContainer
148 width: control.width
149 height: _sliderHeight
150
151 // Major tick marks
152 Repeater {
153 model: _cMajorTicks
154
155 Item {
156 width: sliderContainer.width
157 height: 1
158 y: _majorTickPixelHeight * index + _firstTickPixelOffset
159 opacity: tickValue < _sliderMinVal || tickValue > _sliderMaxVal ? 0.5 : 1
160
161 property real tickValue: _majorTickMaxValue - (_majorTickValueStep * index)
162
163 Rectangle {
164 id: majorTick
165 width: _majorTickWidth
166 height: 1
167 color: _qgcPal.text
168 }
169
170 QGCLabel {
171 anchors.margins: _tickValueRightMargin
172 anchors.right: parent.right
173 anchors.verticalCenter: majorTick.verticalCenter
174 text: parent.tickValue
175 font.pointSize: ScreenTools.largeFontPointSize
176 }
177 }
178 }
179
180 // Minor tick marks
181 Repeater {
182 model: _cMajorTicks * 2
183
184 Rectangle {
185 y: _majorTickPixelHeight / 2 * index + + _firstTickPixelOffset
186 width: _minorTickWidth
187 height: 1
188 color: _qgcPal.text
189 opacity: tickValue < _sliderMinVal || tickValue > _sliderMaxVal ? 0.5 : 1
190 visible: index % 2 === 1
191
192 property real tickValue: _majorTickMaxValue - ((_majorTickValueStep / 2) * index)
193 }
194 }
195 }
196 }
197 }
198
199 // Value indicator
200 Canvas {
201 id: indicatorCanvas
202 y: sliderFlickable.y + _indicatorCenterPos - height / 2
203 width: Math.max(minIndicatorWidth, maxMajorTickDisplayWidth)
204 height: indicatorHeight
205 clip: false
206
207 QGCLabel {
208 id: maxDigitsTextMeasure
209 text: "-100"
210 font.pointSize: ScreenTools.largeFontPointSize
211 visible: false
212 }
213
214 property real indicatorValueMargins: ScreenTools.defaultFontPixelWidth / 2
215 property real indicatorHeight: valueLabel.contentHeight
216 property real pointerWidth: ScreenTools.defaultFontPixelWidth
217 property real minIndicatorWidth: pointerWidth + (indicatorValueMargins * 2) + valueLabel.contentWidth
218 property real maxDigitsWidth: maxDigitsTextMeasure.contentWidth
219 property real intraTickDigitSpacing: ScreenTools.defaultFontPixelWidth
220 property real maxMajorTickDisplayWidth: _majorTickWidth + intraTickDigitSpacing + maxDigitsWidth + _tickValueRightMargin
221
222 onPaint: {
223 var ctx = getContext("2d")
224 ctx.strokeStyle = _qgcPal.text
225 ctx.fillStyle = _qgcPal.window
226 ctx.lineWidth = 1
227 ctx.beginPath()
228 ctx.moveTo(0, indicatorHeight / 2)
229 ctx.lineTo(pointerWidth, indicatorHeight / 4)
230 ctx.lineTo(pointerWidth, 1)
231 ctx.lineTo(width - 1, 1)
232 ctx.lineTo(width - 1, indicatorHeight - 1)
233 ctx.lineTo(pointerWidth, indicatorHeight - 1)
234 ctx.lineTo(pointerWidth, indicatorHeight / 4 * 3)
235 ctx.closePath()
236 ctx.fill()
237 ctx.stroke()
238 }
239
240 // Repaint when palette changes
241 Connections {
242 target: _qgcPal
243 function onPaletteChanged() {
244 indicatorCanvas.requestPaint()
245 }
246 }
247
248 QGCLabel {
249 id: valueLabel
250 anchors.margins: indicatorCanvas.indicatorValueMargins
251 anchors.right: parent.right
252 anchors.verticalCenter: parent.verticalCenter
253 horizontalAlignment: Text.AlignRight
254 verticalAlignment: Text.AlignVCenter
255 text: _clampedSliderValueString(_sliderValue) + " " + unitsString
256 font.pointSize: ScreenTools.largeFontPointSize
257
258 property var unitsString: _sliderType === GuidedValueSlider.Speed ?
259 QGroundControl.unitsConversion.appSettingsSpeedUnitsString :
260 QGroundControl.unitsConversion.appSettingsVerticalDistanceUnitsString
261 }
262
263 QGCMouseArea {
264 anchors.fill: parent
265 onClicked: {
266 sliderValueTextField.text = _clampedSliderValueString(_sliderValue)
267 sliderValueTextField.visible = true
268 sliderValueTextField.forceActiveFocus()
269 }
270 }
271
272 QGCTextField {
273 id: sliderValueTextField
274 anchors.leftMargin: indicatorCanvas.pointerWidth
275 anchors.fill: parent
276 showUnits: true
277 unitsLabel: valueLabel.unitsString
278 visible: false
279 numericValuesOnly: true
280
281 onEditingFinished: {
282 visible = false
283 focus = false
284 setCurrentValue(parseFloat(_clampedSliderValueString(parseFloat(text))))
285 }
286
287 Connections {
288 target: control
289 function on_SliderValueChanged() { sliderValueTextField.visible = false }
290 }
291 }
292 }
293
294 ColumnLayout {
295 id: mainLayout
296 anchors.bottom: parent.bottom
297 spacing: 0
298 width: 200
299 }
300}