1/**************************************************************************** 2** 3** Copyright (C) 2016 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the Qt Quick Extras module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40import QtQuick 2.2 41import QtQuick.Controls 1.4 42import QtQuick.Controls.Styles 1.4 43import QtQuick.Controls.Private 1.0 44import QtQuick.Extras 1.4 45import QtQuick.Extras.Private 1.0 46 47/*! 48 \qmltype GaugeStyle 49 \inqmlmodule QtQuick.Controls.Styles 50 \since 5.5 51 \ingroup controlsstyling 52 \brief Provides custom styling for Gauge. 53 54 You can create a custom gauge by replacing the following delegates: 55 \list 56 \li \l background 57 \li valueBar 58 \li tickmarkLabel 59 \endlist 60 61 Below, you'll find an example of how to create a temperature gauge that 62 changes color as its value increases: 63 64 \code 65 import QtQuick 2.2 66 import QtQuick.Controls 1.4 67 import QtQuick.Controls.Styles 1.4 68 import QtQuick.Extras 1.4 69 70 Rectangle { 71 width: 80 72 height: 200 73 74 Timer { 75 running: true 76 repeat: true 77 interval: 2000 78 onTriggered: gauge.value = gauge.value == gauge.maximumValue ? 5 : gauge.maximumValue 79 } 80 81 Gauge { 82 id: gauge 83 anchors.fill: parent 84 anchors.margins: 10 85 86 value: 5 87 Behavior on value { 88 NumberAnimation { 89 duration: 1000 90 } 91 } 92 93 style: GaugeStyle { 94 valueBar: Rectangle { 95 implicitWidth: 16 96 color: Qt.rgba(gauge.value / gauge.maximumValue, 0, 1 - gauge.value / gauge.maximumValue, 1) 97 } 98 } 99 } 100 } 101 \endcode 102 103 \image gauge-temperature.png 104 The gauge displaying values at various points during the animation. 105 106 \sa {Styling Gauge} 107*/ 108 109Style { 110 id: gaugeStyle 111 112 /*! 113 The \l Gauge that this style is attached to. 114 */ 115 readonly property Gauge control: __control 116 117 /*! 118 This property holds the value displayed by the gauge as a position in 119 pixels. 120 121 It is useful for custom styling. 122 */ 123 readonly property real valuePosition: control.__panel.valuePosition 124 125 /*! 126 The background of the gauge, displayed behind the \l valueBar. 127 128 By default, no background is defined. 129 */ 130 property Component background 131 132 /*! 133 Each tickmark displayed by the gauge. 134 135 To set the size of the tickmarks, specify an 136 \l {Item::implicitWidth}{implicitWidth} and 137 \l {Item::implicitHeight}{implicitHeight}. 138 139 The widest tickmark will determine the space set aside for all 140 tickmarks. For this reason, the \c implicitWidth of each tickmark 141 should be greater than or equal to that of each minor tickmark. If you 142 need minor tickmarks to have greater widths than the major tickmarks, 143 set the larger width in a child item of the \l minorTickmark component. 144 145 For layouting reasons, each tickmark should have the same 146 \c implicitHeight. If different heights are needed for individual 147 tickmarks, specify those heights in a child item of the component. 148 149 In the example below, we decrease the height of the tickmarks: 150 151 \code 152 tickmark: Item { 153 implicitWidth: 18 154 implicitHeight: 1 155 156 Rectangle { 157 color: "#c8c8c8" 158 anchors.fill: parent 159 anchors.leftMargin: 3 160 anchors.rightMargin: 3 161 } 162 } 163 \endcode 164 165 \image gauge-tickmark-example.png Gauge tickmark example 166 167 Each instance of this component has access to the following properties: 168 169 \table 170 \row \li \c {readonly property int} \b styleData.index 171 \li The index of this tickmark. 172 \row \li \c {readonly property real} \b styleData.value 173 \li The value that this tickmark represents. 174 \row \li \c {readonly property real} \b styleData.valuePosition 175 \li The value that this tickmark represents as a position in 176 pixels, with 0 being at the bottom of the gauge. 177 \endtable 178 179 \sa minorTickmark 180 */ 181 property Component tickmark: Item { 182 implicitWidth: Math.round(TextSingleton.height * 1.1) 183 implicitHeight: Math.max(2, Math.round(TextSingleton.height * 0.1)) 184 185 Rectangle { 186 color: "#c8c8c8" 187 anchors.fill: parent 188 anchors.leftMargin: Math.round(TextSingleton.implicitHeight * 0.2) 189 anchors.rightMargin: Math.round(TextSingleton.implicitHeight * 0.2) 190 } 191 } 192 193 /*! 194 Each minor tickmark displayed by the gauge. 195 196 To set the size of the minor tickmarks, specify an 197 \l {Item::implicitWidth}{implicitWidth} and 198 \l {Item::implicitHeight}{implicitHeight}. 199 200 For layouting reasons, each minor tickmark should have the same 201 \c implicitHeight. If different heights are needed for individual 202 tickmarks, specify those heights in a child item of the component. 203 204 In the example below, we decrease the width of the minor tickmarks: 205 206 \code 207 minorTickmark: Item { 208 implicitWidth: 8 209 implicitHeight: 1 210 211 Rectangle { 212 color: "#cccccc" 213 anchors.fill: parent 214 anchors.leftMargin: 2 215 anchors.rightMargin: 4 216 } 217 } 218 \endcode 219 220 \image gauge-minorTickmark-example.png Gauge minorTickmark example 221 222 Each instance of this component has access to the following property: 223 224 \table 225 \row \li \c {readonly property int} \b styleData.index 226 \li The index of this minor tickmark. 227 \row \li \c {readonly property real} \b styleData.value 228 \li The value that this minor tickmark represents. 229 \row \li \c {readonly property real} \b styleData.valuePosition 230 \li The value that this minor tickmark represents as a 231 position in pixels, with 0 being at the bottom of the 232 gauge. 233 \endtable 234 235 \sa tickmark 236 */ 237 property Component minorTickmark: Item { 238 implicitWidth: Math.round(TextSingleton.implicitHeight * 0.65) 239 implicitHeight: Math.max(1, Math.round(TextSingleton.implicitHeight * 0.05)) 240 241 Rectangle { 242 color: "#c8c8c8" 243 anchors.fill: parent 244 anchors.leftMargin: control.__tickmarkAlignment === Qt.AlignBottom || control.__tickmarkAlignment === Qt.AlignRight 245 ? Math.max(3, Math.round(TextSingleton.implicitHeight * 0.2)) 246 : 0 247 anchors.rightMargin: control.__tickmarkAlignment === Qt.AlignBottom || control.__tickmarkAlignment === Qt.AlignRight 248 ? 0 249 : Math.max(3, Math.round(TextSingleton.implicitHeight * 0.2)) 250 } 251 } 252 253 /*! 254 This defines the text of each tickmark label on the gauge. 255 256 Each instance of this component has access to the following properties: 257 258 \table 259 \row \li \c {readonly property int} \b styleData.index 260 \li The index of this label. 261 \row \li \c {readonly property real} \b styleData.value 262 \li The value that this label represents. 263 \endtable 264 */ 265 property Component tickmarkLabel: Text { 266 text: control.formatValue(styleData.value) 267 font: control.font 268 color: "#c8c8c8" 269 antialiasing: true 270 } 271 272 /*! 273 The bar that represents the value of the gauge. 274 275 To height of the value bar is automatically resized according to 276 \l {Gauge::value}{value}, and does not need to be specified. 277 278 When a custom valueBar is defined, its 279 \l {Item::implicitWidth}{implicitWidth} property must be set. 280 */ 281 property Component valueBar: Rectangle { 282 color: "#00bbff" 283 implicitWidth: TextSingleton.implicitHeight 284 } 285 286 /*! 287 The bar that represents the foreground of the gauge. 288 289 This component is drawn above every other component. 290 */ 291 property Component foreground: Canvas { 292 readonly property real xCenter: width / 2 293 readonly property real yCenter: height / 2 294 property real shineLength: height * 0.95 295 296 onPaint: { 297 var ctx = getContext("2d"); 298 ctx.reset(); 299 300 ctx.beginPath(); 301 ctx.rect(0, 0, width, height); 302 303 var gradient = ctx.createLinearGradient(0, yCenter, width, yCenter); 304 305 gradient.addColorStop(0, Qt.rgba(1, 1, 1, 0.08)); 306 gradient.addColorStop(1, Qt.rgba(1, 1, 1, 0.20)); 307 ctx.fillStyle = gradient; 308 ctx.fill(); 309 } 310 } 311 312 /*! \internal */ 313 property Component panel: Item { 314 id: panelComponent 315 implicitWidth: control.orientation === Qt.Vertical ? tickmarkLabelBoundsWidth + rawBarWidth : TextSingleton.height * 14 316 implicitHeight: control.orientation === Qt.Vertical ? TextSingleton.height * 14 : tickmarkLabelBoundsWidth + rawBarWidth 317 318 readonly property int tickmarkCount: (control.maximumValue - control.minimumValue) / control.tickmarkStepSize + 1 319 readonly property real tickmarkSpacing: (tickmarkLabelBounds.height - tickmarkWidth * tickmarkCount) / (tickmarkCount - 1) 320 321 property real tickmarkLength: tickmarkColumn.width 322 // Can't deduce this from the column, so we set it from within the first tickmark delegate loader. 323 property real tickmarkWidth: 2 324 325 readonly property real tickmarkOffset: control.orientation === Qt.Vertical ? control.__hiddenText.height / 2 : control.__hiddenText.width / 2 326 327 readonly property real minorTickmarkStep: control.tickmarkStepSize / (control.minorTickmarkCount + 1); 328 329 /*! 330 Returns the marker text that should be displayed based on 331 \a markerPos (\c 0 to \c 1.0). 332 */ 333 function markerTextFromPos(markerPos) { 334 return markerPos * (control.maximumValue - control.minimumValue) + control.minimumValue; 335 } 336 337 readonly property real rawBarWidth: valueBarLoader.item.implicitWidth 338 readonly property real barLength: (control.orientation === Qt.Vertical ? control.height : control.width) - (tickmarkOffset * 2 - 2) 339 340 readonly property real tickmarkLabelBoundsWidth: tickmarkLength + (control.orientation === Qt.Vertical ? control.__hiddenText.width : control.__hiddenText.height) 341 readonly property int valuePosition: valueBarLoader.height 342 343 Item { 344 id: container 345 346 width: control.orientation === Qt.Vertical ? parent.width : parent.height 347 height: control.orientation === Qt.Vertical ? parent.height : parent.width 348 rotation: control.orientation === Qt.Horizontal ? 90 : 0 349 transformOrigin: Item.Center 350 anchors.centerIn: parent 351 352 Item { 353 id: valueBarItem 354 355 x: control.__tickmarkAlignment === Qt.AlignLeft || control.__tickmarkAlignment === Qt.AlignTop ? tickmarkLabelBounds.x + tickmarkLabelBounds.width : 0 356 width: rawBarWidth 357 height: barLength 358 anchors.verticalCenter: parent.verticalCenter 359 360 Loader { 361 id: backgroundLoader 362 sourceComponent: background 363 anchors.fill: parent 364 } 365 366 Loader { 367 id: valueBarLoader 368 sourceComponent: valueBar 369 370 readonly property real valueAsPercentage: (control.value - control.minimumValue) / (control.maximumValue - control.minimumValue) 371 372 y: Math.round(parent.height - height) 373 height: Math.round(valueAsPercentage * parent.height) 374 } 375 } 376 Item { 377 id: tickmarkLabelBounds 378 379 x: control.__tickmarkAlignment === Qt.AlignLeft || control.__tickmarkAlignment === Qt.AlignTop ? 0 : valueBarItem.width 380 width: tickmarkLabelBoundsWidth 381 height: barLength 382 anchors.verticalCenter: parent.verticalCenter 383 // We want our items to be laid out from bottom to top, but Column can't do that, so we flip 384 // the whole item containing the tickmarks and labels vertically. Then, we flip each tickmark 385 // and label back again. 386 transform: Rotation { 387 axis.x: 1 388 axis.y: 0 389 axis.z: 0 390 origin.x: tickmarkLabelBounds.width / 2 391 origin.y: tickmarkLabelBounds.height / 2 392 angle: 180 393 } 394 395 Column { 396 id: tickmarkColumn 397 x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom ? 0 : tickmarkLabelBounds.width - width 398 spacing: tickmarkSpacing 399 anchors.verticalCenter: parent.verticalCenter 400 401 Repeater { 402 id: tickmarkRepeater 403 model: tickmarkCount 404 delegate: Loader { 405 id: tickmarkDelegateLoader 406 407 sourceComponent: gaugeStyle.tickmark 408 transform: Rotation { 409 axis.x: 1 410 axis.y: 0 411 axis.z: 0 412 origin.x: tickmarkDelegateLoader.width / 2 413 origin.y: tickmarkDelegateLoader.height / 2 414 angle: 180 415 } 416 417 onHeightChanged: { 418 if (index == 0) 419 tickmarkWidth = height; 420 } 421 422 readonly property int __index: index 423 property QtObject styleData: QtObject { 424 readonly property alias index: tickmarkDelegateLoader.__index 425 readonly property real value: (index / (tickmarkCount - 1)) * (control.maximumValue - control.minimumValue) + control.minimumValue 426 readonly property int valuePosition: Math.round(tickmarkDelegateLoader.y) 427 } 428 } 429 } 430 } 431 432 // Doesn't need to be in a column, since we assume that the major tickmarks will always be longer than us. 433 Repeater { 434 id: minorTickmarkRepeater 435 model: (tickmarkCount - 1) * control.minorTickmarkCount 436 delegate: Loader { 437 id: minorTickmarkDelegateLoader 438 439 x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom ? 0 : tickmarkLabelBounds.width - width 440 y: { 441 var tickmarkWidthOffset = Math.floor(index / control.minorTickmarkCount) * tickmarkWidth + tickmarkWidth; 442 var relativePosition = (index % control.minorTickmarkCount + 1) * (tickmarkSpacing / (control.minorTickmarkCount + 1)); 443 var clusterOffset = Math.floor(index / control.minorTickmarkCount) * tickmarkSpacing; 444 // We assume that each minorTickmark's height is the same. 445 return clusterOffset + tickmarkWidthOffset + relativePosition - height / 2; 446 } 447 448 transform: Rotation { 449 axis.x: 1 450 axis.y: 0 451 axis.z: 0 452 origin.x: minorTickmarkDelegateLoader.width / 2 453 origin.y: minorTickmarkDelegateLoader.height / 2 454 angle: 180 455 } 456 457 sourceComponent: gaugeStyle.minorTickmark 458 459 readonly property int __index: index 460 property QtObject styleData: QtObject { 461 readonly property alias index: minorTickmarkDelegateLoader.__index 462 readonly property real value: { 463 var tickmarkIndex = Math.floor(index / control.minorTickmarkCount); 464 return index * minorTickmarkStep + minorTickmarkStep * tickmarkIndex + minorTickmarkStep + control.minimumValue; 465 } 466 readonly property int valuePosition: Math.round(minorTickmarkDelegateLoader.y) 467 } 468 } 469 } 470 471 Item { 472 id: tickmarkLabelItem 473 x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom 474 ? tickmarkLength 475 : tickmarkLabelBounds.width - tickmarkLength - width 476 width: control.__hiddenText.width 477 // Use the bar height instead of the container's, as the labels seem to be translated by 1 when we 478 // flip the control vertically, and this fixes that. 479 height: parent.height 480 anchors.verticalCenter: parent.verticalCenter 481 482 Repeater { 483 id: tickmarkTextRepeater 484 model: tickmarkCount 485 delegate: Item { 486 x: { 487 if (control.orientation === Qt.Vertical) 488 return 0; 489 490 // Align the text to the edge of the tickmarks. 491 return ((width - height) / 2) * (control.__tickmarkAlignment === Qt.AlignBottom ? -1 : 1); 492 } 493 y: index * labelDistance - height / 2 494 495 width: control.__hiddenText.width 496 height: control.__hiddenText.height 497 498 transformOrigin: Item.Center 499 rotation: control.orientation === Qt.Vertical ? 0 : 90 500 501 readonly property real labelDistance: tickmarkLabelBounds.height / (tickmarkCount - 1) 502 503 Loader { 504 id: tickmarkTextRepeaterDelegate 505 506 x: { 507 if (control.orientation === Qt.Horizontal) { 508 return parent.width / 2 - width / 2; 509 } 510 511 return control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom 512 ? 0 513 : parent.width - width; 514 } 515 516 transform: Rotation { 517 axis.x: 1 518 axis.y: 0 519 axis.z: 0 520 origin.x: tickmarkTextRepeaterDelegate.width / 2 521 origin.y: tickmarkTextRepeaterDelegate.height / 2 522 angle: 180 523 } 524 525 sourceComponent: tickmarkLabel 526 527 readonly property int __index: index 528 property QtObject styleData: QtObject { 529 readonly property alias index: tickmarkTextRepeaterDelegate.__index 530 readonly property real value: markerTextFromPos(index / (tickmarkTextRepeater.count - 1)) 531 } 532 } 533 } 534 } 535 } 536 } 537 Loader { 538 id: foregroundLoader 539 sourceComponent: foreground 540 anchors.fill: valueBarItem 541 } 542 } 543 } 544} 545