/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ 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 } }