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