545 lines
22 KiB
QML
545 lines
22 KiB
QML
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the Qt Quick Extras module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
import QtQuick 2.2
|
|
import QtQuick.Controls 1.4
|
|
import QtQuick.Controls.Styles 1.4
|
|
import QtQuick.Controls.Private 1.0
|
|
import QtQuick.Extras 1.4
|
|
import QtQuick.Extras.Private 1.0
|
|
|
|
/*!
|
|
\qmltype GaugeStyle
|
|
\inqmlmodule QtQuick.Controls.Styles
|
|
\since 5.5
|
|
\ingroup controlsstyling
|
|
\brief Provides custom styling for Gauge.
|
|
|
|
You can create a custom gauge by replacing the following delegates:
|
|
\list
|
|
\li \l background
|
|
\li valueBar
|
|
\li tickmarkLabel
|
|
\endlist
|
|
|
|
Below, you'll find an example of how to create a temperature gauge that
|
|
changes color as its value increases:
|
|
|
|
\code
|
|
import QtQuick 2.2
|
|
import QtQuick.Controls 1.4
|
|
import QtQuick.Controls.Styles 1.4
|
|
import QtQuick.Extras 1.4
|
|
|
|
Rectangle {
|
|
width: 80
|
|
height: 200
|
|
|
|
Timer {
|
|
running: true
|
|
repeat: true
|
|
interval: 2000
|
|
onTriggered: gauge.value = gauge.value == gauge.maximumValue ? 5 : gauge.maximumValue
|
|
}
|
|
|
|
Gauge {
|
|
id: gauge
|
|
anchors.fill: parent
|
|
anchors.margins: 10
|
|
|
|
value: 5
|
|
Behavior on value {
|
|
NumberAnimation {
|
|
duration: 1000
|
|
}
|
|
}
|
|
|
|
style: GaugeStyle {
|
|
valueBar: Rectangle {
|
|
implicitWidth: 16
|
|
color: Qt.rgba(gauge.value / gauge.maximumValue, 0, 1 - gauge.value / gauge.maximumValue, 1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
\image gauge-temperature.png
|
|
The gauge displaying values at various points during the animation.
|
|
|
|
\sa {Styling Gauge}
|
|
*/
|
|
|
|
Style {
|
|
id: gaugeStyle
|
|
|
|
/*!
|
|
The \l Gauge that this style is attached to.
|
|
*/
|
|
readonly property Gauge control: __control
|
|
|
|
/*!
|
|
This property holds the value displayed by the gauge as a position in
|
|
pixels.
|
|
|
|
It is useful for custom styling.
|
|
*/
|
|
readonly property real valuePosition: control.__panel.valuePosition
|
|
|
|
/*!
|
|
The background of the gauge, displayed behind the \l valueBar.
|
|
|
|
By default, no background is defined.
|
|
*/
|
|
property Component background
|
|
|
|
/*!
|
|
Each tickmark displayed by the gauge.
|
|
|
|
To set the size of the tickmarks, specify an
|
|
\l {Item::implicitWidth}{implicitWidth} and
|
|
\l {Item::implicitHeight}{implicitHeight}.
|
|
|
|
The widest tickmark will determine the space set aside for all
|
|
tickmarks. For this reason, the \c implicitWidth of each tickmark
|
|
should be greater than or equal to that of each minor tickmark. If you
|
|
need minor tickmarks to have greater widths than the major tickmarks,
|
|
set the larger width in a child item of the \l minorTickmark component.
|
|
|
|
For layouting reasons, each tickmark should have the same
|
|
\c implicitHeight. If different heights are needed for individual
|
|
tickmarks, specify those heights in a child item of the component.
|
|
|
|
In the example below, we decrease the height of the tickmarks:
|
|
|
|
\code
|
|
tickmark: Item {
|
|
implicitWidth: 18
|
|
implicitHeight: 1
|
|
|
|
Rectangle {
|
|
color: "#c8c8c8"
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 3
|
|
anchors.rightMargin: 3
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
\image gauge-tickmark-example.png Gauge tickmark example
|
|
|
|
Each instance of this component has access to the following properties:
|
|
|
|
\table
|
|
\row \li \c {readonly property int} \b styleData.index
|
|
\li The index of this tickmark.
|
|
\row \li \c {readonly property real} \b styleData.value
|
|
\li The value that this tickmark represents.
|
|
\row \li \c {readonly property real} \b styleData.valuePosition
|
|
\li The value that this tickmark represents as a position in
|
|
pixels, with 0 being at the bottom of the gauge.
|
|
\endtable
|
|
|
|
\sa minorTickmark
|
|
*/
|
|
property Component tickmark: Item {
|
|
implicitWidth: Math.round(TextSingleton.height * 1.1)
|
|
implicitHeight: Math.max(2, Math.round(TextSingleton.height * 0.1))
|
|
|
|
Rectangle {
|
|
color: "#c8c8c8"
|
|
anchors.fill: parent
|
|
anchors.leftMargin: Math.round(TextSingleton.implicitHeight * 0.2)
|
|
anchors.rightMargin: Math.round(TextSingleton.implicitHeight * 0.2)
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Each minor tickmark displayed by the gauge.
|
|
|
|
To set the size of the minor tickmarks, specify an
|
|
\l {Item::implicitWidth}{implicitWidth} and
|
|
\l {Item::implicitHeight}{implicitHeight}.
|
|
|
|
For layouting reasons, each minor tickmark should have the same
|
|
\c implicitHeight. If different heights are needed for individual
|
|
tickmarks, specify those heights in a child item of the component.
|
|
|
|
In the example below, we decrease the width of the minor tickmarks:
|
|
|
|
\code
|
|
minorTickmark: Item {
|
|
implicitWidth: 8
|
|
implicitHeight: 1
|
|
|
|
Rectangle {
|
|
color: "#cccccc"
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 2
|
|
anchors.rightMargin: 4
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
\image gauge-minorTickmark-example.png Gauge minorTickmark example
|
|
|
|
Each instance of this component has access to the following property:
|
|
|
|
\table
|
|
\row \li \c {readonly property int} \b styleData.index
|
|
\li The index of this minor tickmark.
|
|
\row \li \c {readonly property real} \b styleData.value
|
|
\li The value that this minor tickmark represents.
|
|
\row \li \c {readonly property real} \b styleData.valuePosition
|
|
\li The value that this minor tickmark represents as a
|
|
position in pixels, with 0 being at the bottom of the
|
|
gauge.
|
|
\endtable
|
|
|
|
\sa tickmark
|
|
*/
|
|
property Component minorTickmark: Item {
|
|
implicitWidth: Math.round(TextSingleton.implicitHeight * 0.65)
|
|
implicitHeight: Math.max(1, Math.round(TextSingleton.implicitHeight * 0.05))
|
|
|
|
Rectangle {
|
|
color: "#c8c8c8"
|
|
anchors.fill: parent
|
|
anchors.leftMargin: control.__tickmarkAlignment === Qt.AlignBottom || control.__tickmarkAlignment === Qt.AlignRight
|
|
? Math.max(3, Math.round(TextSingleton.implicitHeight * 0.2))
|
|
: 0
|
|
anchors.rightMargin: control.__tickmarkAlignment === Qt.AlignBottom || control.__tickmarkAlignment === Qt.AlignRight
|
|
? 0
|
|
: Math.max(3, Math.round(TextSingleton.implicitHeight * 0.2))
|
|
}
|
|
}
|
|
|
|
/*!
|
|
This defines the text of each tickmark label on the gauge.
|
|
|
|
Each instance of this component has access to the following properties:
|
|
|
|
\table
|
|
\row \li \c {readonly property int} \b styleData.index
|
|
\li The index of this label.
|
|
\row \li \c {readonly property real} \b styleData.value
|
|
\li The value that this label represents.
|
|
\endtable
|
|
*/
|
|
property Component tickmarkLabel: Text {
|
|
text: control.formatValue(styleData.value)
|
|
font: control.font
|
|
color: "#c8c8c8"
|
|
antialiasing: true
|
|
}
|
|
|
|
/*!
|
|
The bar that represents the value of the gauge.
|
|
|
|
To height of the value bar is automatically resized according to
|
|
\l {Gauge::value}{value}, and does not need to be specified.
|
|
|
|
When a custom valueBar is defined, its
|
|
\l {Item::implicitWidth}{implicitWidth} property must be set.
|
|
*/
|
|
property Component valueBar: Rectangle {
|
|
color: "#00bbff"
|
|
implicitWidth: TextSingleton.implicitHeight
|
|
}
|
|
|
|
/*!
|
|
The bar that represents the foreground of the gauge.
|
|
|
|
This component is drawn above every other component.
|
|
*/
|
|
property Component foreground: Canvas {
|
|
readonly property real xCenter: width / 2
|
|
readonly property real yCenter: height / 2
|
|
property real shineLength: height * 0.95
|
|
|
|
onPaint: {
|
|
var ctx = getContext("2d");
|
|
ctx.reset();
|
|
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, width, height);
|
|
|
|
var gradient = ctx.createLinearGradient(0, yCenter, width, yCenter);
|
|
|
|
gradient.addColorStop(0, Qt.rgba(1, 1, 1, 0.08));
|
|
gradient.addColorStop(1, Qt.rgba(1, 1, 1, 0.20));
|
|
ctx.fillStyle = gradient;
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
/*! \internal */
|
|
property Component panel: Item {
|
|
id: panelComponent
|
|
implicitWidth: control.orientation === Qt.Vertical ? tickmarkLabelBoundsWidth + rawBarWidth : TextSingleton.height * 14
|
|
implicitHeight: control.orientation === Qt.Vertical ? TextSingleton.height * 14 : tickmarkLabelBoundsWidth + rawBarWidth
|
|
|
|
readonly property int tickmarkCount: (control.maximumValue - control.minimumValue) / control.tickmarkStepSize + 1
|
|
readonly property real tickmarkSpacing: (tickmarkLabelBounds.height - tickmarkWidth * tickmarkCount) / (tickmarkCount - 1)
|
|
|
|
property real tickmarkLength: tickmarkColumn.width
|
|
// Can't deduce this from the column, so we set it from within the first tickmark delegate loader.
|
|
property real tickmarkWidth: 2
|
|
|
|
readonly property real tickmarkOffset: control.orientation === Qt.Vertical ? control.__hiddenText.height / 2 : control.__hiddenText.width / 2
|
|
|
|
readonly property real minorTickmarkStep: control.tickmarkStepSize / (control.minorTickmarkCount + 1);
|
|
|
|
/*!
|
|
Returns the marker text that should be displayed based on
|
|
\a markerPos (\c 0 to \c 1.0).
|
|
*/
|
|
function markerTextFromPos(markerPos) {
|
|
return markerPos * (control.maximumValue - control.minimumValue) + control.minimumValue;
|
|
}
|
|
|
|
readonly property real rawBarWidth: valueBarLoader.item.implicitWidth
|
|
readonly property real barLength: (control.orientation === Qt.Vertical ? control.height : control.width) - (tickmarkOffset * 2 - 2)
|
|
|
|
readonly property real tickmarkLabelBoundsWidth: tickmarkLength + (control.orientation === Qt.Vertical ? control.__hiddenText.width : control.__hiddenText.height)
|
|
readonly property int valuePosition: valueBarLoader.height
|
|
|
|
Item {
|
|
id: container
|
|
|
|
width: control.orientation === Qt.Vertical ? parent.width : parent.height
|
|
height: control.orientation === Qt.Vertical ? parent.height : parent.width
|
|
rotation: control.orientation === Qt.Horizontal ? 90 : 0
|
|
transformOrigin: Item.Center
|
|
anchors.centerIn: parent
|
|
|
|
Item {
|
|
id: valueBarItem
|
|
|
|
x: control.__tickmarkAlignment === Qt.AlignLeft || control.__tickmarkAlignment === Qt.AlignTop ? tickmarkLabelBounds.x + tickmarkLabelBounds.width : 0
|
|
width: rawBarWidth
|
|
height: barLength
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Loader {
|
|
id: backgroundLoader
|
|
sourceComponent: background
|
|
anchors.fill: parent
|
|
}
|
|
|
|
Loader {
|
|
id: valueBarLoader
|
|
sourceComponent: valueBar
|
|
|
|
readonly property real valueAsPercentage: (control.value - control.minimumValue) / (control.maximumValue - control.minimumValue)
|
|
|
|
y: Math.round(parent.height - height)
|
|
height: Math.round(valueAsPercentage * parent.height)
|
|
}
|
|
}
|
|
Item {
|
|
id: tickmarkLabelBounds
|
|
|
|
x: control.__tickmarkAlignment === Qt.AlignLeft || control.__tickmarkAlignment === Qt.AlignTop ? 0 : valueBarItem.width
|
|
width: tickmarkLabelBoundsWidth
|
|
height: barLength
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
// We want our items to be laid out from bottom to top, but Column can't do that, so we flip
|
|
// the whole item containing the tickmarks and labels vertically. Then, we flip each tickmark
|
|
// and label back again.
|
|
transform: Rotation {
|
|
axis.x: 1
|
|
axis.y: 0
|
|
axis.z: 0
|
|
origin.x: tickmarkLabelBounds.width / 2
|
|
origin.y: tickmarkLabelBounds.height / 2
|
|
angle: 180
|
|
}
|
|
|
|
Column {
|
|
id: tickmarkColumn
|
|
x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom ? 0 : tickmarkLabelBounds.width - width
|
|
spacing: tickmarkSpacing
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Repeater {
|
|
id: tickmarkRepeater
|
|
model: tickmarkCount
|
|
delegate: Loader {
|
|
id: tickmarkDelegateLoader
|
|
|
|
sourceComponent: gaugeStyle.tickmark
|
|
transform: Rotation {
|
|
axis.x: 1
|
|
axis.y: 0
|
|
axis.z: 0
|
|
origin.x: tickmarkDelegateLoader.width / 2
|
|
origin.y: tickmarkDelegateLoader.height / 2
|
|
angle: 180
|
|
}
|
|
|
|
onHeightChanged: {
|
|
if (index == 0)
|
|
tickmarkWidth = height;
|
|
}
|
|
|
|
readonly property int __index: index
|
|
property QtObject styleData: QtObject {
|
|
readonly property alias index: tickmarkDelegateLoader.__index
|
|
readonly property real value: (index / (tickmarkCount - 1)) * (control.maximumValue - control.minimumValue) + control.minimumValue
|
|
readonly property int valuePosition: Math.round(tickmarkDelegateLoader.y)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Doesn't need to be in a column, since we assume that the major tickmarks will always be longer than us.
|
|
Repeater {
|
|
id: minorTickmarkRepeater
|
|
model: (tickmarkCount - 1) * control.minorTickmarkCount
|
|
delegate: Loader {
|
|
id: minorTickmarkDelegateLoader
|
|
|
|
x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom ? 0 : tickmarkLabelBounds.width - width
|
|
y: {
|
|
var tickmarkWidthOffset = Math.floor(index / control.minorTickmarkCount) * tickmarkWidth + tickmarkWidth;
|
|
var relativePosition = (index % control.minorTickmarkCount + 1) * (tickmarkSpacing / (control.minorTickmarkCount + 1));
|
|
var clusterOffset = Math.floor(index / control.minorTickmarkCount) * tickmarkSpacing;
|
|
// We assume that each minorTickmark's height is the same.
|
|
return clusterOffset + tickmarkWidthOffset + relativePosition - height / 2;
|
|
}
|
|
|
|
transform: Rotation {
|
|
axis.x: 1
|
|
axis.y: 0
|
|
axis.z: 0
|
|
origin.x: minorTickmarkDelegateLoader.width / 2
|
|
origin.y: minorTickmarkDelegateLoader.height / 2
|
|
angle: 180
|
|
}
|
|
|
|
sourceComponent: gaugeStyle.minorTickmark
|
|
|
|
readonly property int __index: index
|
|
property QtObject styleData: QtObject {
|
|
readonly property alias index: minorTickmarkDelegateLoader.__index
|
|
readonly property real value: {
|
|
var tickmarkIndex = Math.floor(index / control.minorTickmarkCount);
|
|
return index * minorTickmarkStep + minorTickmarkStep * tickmarkIndex + minorTickmarkStep + control.minimumValue;
|
|
}
|
|
readonly property int valuePosition: Math.round(minorTickmarkDelegateLoader.y)
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: tickmarkLabelItem
|
|
x: control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom
|
|
? tickmarkLength
|
|
: tickmarkLabelBounds.width - tickmarkLength - width
|
|
width: control.__hiddenText.width
|
|
// Use the bar height instead of the container's, as the labels seem to be translated by 1 when we
|
|
// flip the control vertically, and this fixes that.
|
|
height: parent.height
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Repeater {
|
|
id: tickmarkTextRepeater
|
|
model: tickmarkCount
|
|
delegate: Item {
|
|
x: {
|
|
if (control.orientation === Qt.Vertical)
|
|
return 0;
|
|
|
|
// Align the text to the edge of the tickmarks.
|
|
return ((width - height) / 2) * (control.__tickmarkAlignment === Qt.AlignBottom ? -1 : 1);
|
|
}
|
|
y: index * labelDistance - height / 2
|
|
|
|
width: control.__hiddenText.width
|
|
height: control.__hiddenText.height
|
|
|
|
transformOrigin: Item.Center
|
|
rotation: control.orientation === Qt.Vertical ? 0 : 90
|
|
|
|
readonly property real labelDistance: tickmarkLabelBounds.height / (tickmarkCount - 1)
|
|
|
|
Loader {
|
|
id: tickmarkTextRepeaterDelegate
|
|
|
|
x: {
|
|
if (control.orientation === Qt.Horizontal) {
|
|
return parent.width / 2 - width / 2;
|
|
}
|
|
|
|
return control.__tickmarkAlignment === Qt.AlignRight || control.__tickmarkAlignment === Qt.AlignBottom
|
|
? 0
|
|
: parent.width - width;
|
|
}
|
|
|
|
transform: Rotation {
|
|
axis.x: 1
|
|
axis.y: 0
|
|
axis.z: 0
|
|
origin.x: tickmarkTextRepeaterDelegate.width / 2
|
|
origin.y: tickmarkTextRepeaterDelegate.height / 2
|
|
angle: 180
|
|
}
|
|
|
|
sourceComponent: tickmarkLabel
|
|
|
|
readonly property int __index: index
|
|
property QtObject styleData: QtObject {
|
|
readonly property alias index: tickmarkTextRepeaterDelegate.__index
|
|
readonly property real value: markerTextFromPos(index / (tickmarkTextRepeater.count - 1))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Loader {
|
|
id: foregroundLoader
|
|
sourceComponent: foreground
|
|
anchors.fill: valueBarItem
|
|
}
|
|
}
|
|
}
|
|
}
|