Files
qt-creator/lib/qtcreator/qtcomponents/custom/Slider.qml
hjk 2931a499e6 Long live the king!
Change-Id: I2b72b34c0cfeafc8bdbaf49b83ff723544f2b6e2
Reviewed-by: Daniel Teske <daniel.teske@nokia.com>
2012-01-26 19:55:36 +01:00

293 lines
10 KiB
QML

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
import QtQuick 1.0
import "../"
Item {
id: slider
// COMMON API
property int orientation: Qt.Horizontal
property alias minimumValue: range.minimumValue
property alias maximumValue: range.maximumValue
property alias inverted: range.inverted
property bool updateValueWhileDragging: true
property alias pressed: mouseArea.pressed
property alias stepSize: range.stepSize
property alias hoverEnabled: mouseArea.hoverEnabled
// NOTE: this property is in/out, the user can set it, create bindings to it, and
// at the same time the slider wants to update. There's no way in QML to do this kind
// of updates AND allow the user bind it (without a Binding object). That's the
// reason this is an alias to a C++ property in range model.
property alias value: range.value
property bool containsMouse: mouseArea.containsMouse
// CONVENIENCE TO BE USED BY STYLES
property int leftMargin: 0
property int rightMargin: 0
// EXTENSIONS
// Indicate that we want animations in the Slider, people customizing should
// look at it to decide whether or not active animations.
property bool animated: true
property bool activeFocusOnPress: true
// Value indicator displays the current value near the slider
property bool valueIndicatorVisible: true
property int valueIndicatorMargin: 10
property string valueIndicatorPosition: _isVertical ? "Left" : "Top"
// Reimplement this function to control how the value is shown in the
// indicator.
function formatValue(v) {
return Math.round(v);
}
// Hooks for customizing the pieces of the slider
property Component groove: null
property Component handle: null
property Component valueIndicator: null
// PRIVATE/CONVENIENCE
property bool _isVertical: orientation == Qt.Vertical
// This is a template slider, so every piece can be modified by passing a
// different Component. The main elements in the implementation are
//
// - the 'range' does the calculations to map position to/from value,
// it also serves as a data storage for both properties;
//
// - the 'fakeHandle' is what the mouse area drags on the screen, it feeds
// the 'range' position and also reads it when convenient;
//
// - the real 'handle' it is the visual representation of the handle, that
// just follows the 'fakeHandle' position.
//
// When the 'updateValueWhileDragging' is false and we are dragging, we stop
// feeding the range with position information, delaying until the next
// mouse release.
//
// Everything is encapsulated in a contents Item, so for the
// vertical slider, we just swap the height/width, make it
// horizontal, and then use rotation to make it vertical again.
Item {
id: contents
width: _isVertical ? slider.height : slider.width
height: _isVertical ? slider.width : slider.height
rotation: _isVertical ? -90 : 0
anchors.centerIn: slider
RangeModel {
id: range
minimumValue: 0.0
maximumValue: 1.0
value: 0
stepSize: 0.0
inverted: false
positionAtMinimum: leftMargin
positionAtMaximum: contents.width - rightMargin
}
Loader {
id: grooveLoader
anchors.fill: parent
sourceComponent: groove
property real handlePosition : handleLoader.x
function positionForValue(value) {
return range.positionForValue(value) - leftMargin;
}
}
Loader {
id: handleLoader
transform: Translate { x: - handleLoader.width / 2 }
anchors.verticalCenter: grooveLoader.verticalCenter
sourceComponent: handle
x: fakeHandle.x
Behavior on x {
id: behavior
enabled: !mouseArea.drag.active && slider.animated
PropertyAnimation {
duration: behavior.enabled ? 150 : 0
easing.type: Easing.OutSine
}
}
}
Item {
id: fakeHandle
width: handleLoader.width
height: handleLoader.height
transform: Translate { x: - handleLoader.width / 2 }
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.centerIn: parent
anchors.horizontalCenterOffset: (slider.leftMargin - slider.rightMargin) / 2
width: parent.width + handleLoader.width - slider.rightMargin - slider.leftMargin
height: parent.height
drag.target: fakeHandle
drag.axis: Drag.XAxis
drag.minimumX: range.positionAtMinimum
drag.maximumX: range.positionAtMaximum
onPressed: {
if (activeFocusOnPress)
slider.focus = true;
// Clamp the value
var newX = Math.max(mouse.x, drag.minimumX);
newX = Math.min(newX, drag.maximumX);
// Debounce the press: a press event inside the handler will not
// change its position, the user needs to drag it.
// Note this really messes up things for scrollbar
// if (Math.abs(newX - fakeHandle.x) > handleLoader.width / 2)
range.position = newX;
}
onReleased: {
// If we don't update while dragging, this is the only
// moment that the range is updated.
if (!slider.updateValueWhileDragging)
range.position = fakeHandle.x;
}
}
Loader {
id: valueIndicatorLoader
transform: Translate { x: - handleLoader.width / 2 }
rotation: _isVertical ? 90 : 0
visible: valueIndicatorVisible
// Properties available for the delegate component. Note that the indicatorText
// shows the value for the position the handle is, which is not necessarily the
// available as the current slider.value, since updateValueWhileDragging can
// be set to 'false'.
property string indicatorText: slider.formatValue(range.valueForPosition(handleLoader.x))
property bool dragging: mouseArea.drag.active
sourceComponent: valueIndicator
state: {
if (!_isVertical)
return slider.valueIndicatorPosition;
if (valueIndicatorPosition == "Right")
return "Bottom";
if (valueIndicatorPosition == "Top")
return "Right";
if (valueIndicatorPosition == "Bottom")
return "Left";
return "Top";
}
anchors.margins: valueIndicatorMargin
states: [
State {
name: "Top"
AnchorChanges {
target: valueIndicatorLoader
anchors.bottom: handleLoader.top
anchors.horizontalCenter: handleLoader.horizontalCenter
}
},
State {
name: "Bottom"
AnchorChanges {
target: valueIndicatorLoader
anchors.top: handleLoader.bottom
anchors.horizontalCenter: handleLoader.horizontalCenter
}
},
State {
name: "Right"
AnchorChanges {
target: valueIndicatorLoader
anchors.left: handleLoader.right
anchors.verticalCenter: handleLoader.verticalCenter
}
},
State {
name: "Left"
AnchorChanges {
target: valueIndicatorLoader
anchors.right: handleLoader.left
anchors.verticalCenter: handleLoader.verticalCenter
}
}
]
}
}
// Range position normally follow fakeHandle, except when
// 'updateValueWhileDragging' is false. In this case it will only follow
// if the user is not pressing the handle.
Binding {
when: updateValueWhileDragging || !mouseArea.pressed
target: range
property: "position"
value: fakeHandle.x
}
// During the drag, we simply ignore position set from the range, this
// means that setting a value while dragging will not "interrupt" the
// dragging activity.
Binding {
when: !mouseArea.drag.active
target: fakeHandle
property: "x"
value: range.position
}
}