123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- /****************************************************************************
- **
- ** 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 QtQml 2.14 as Qml
- 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
- import QtQuick.Layouts 1.0
- /*!
- \qmltype Tumbler
- \inqmlmodule QtQuick.Extras
- \since 5.5
- \ingroup extras
- \ingroup extras-interactive
- \brief A control that can have several spinnable wheels, each with items
- that can be selected.
- \image tumbler.png A Tumbler
- \note Tumbler requires Qt 5.5.0 or later.
- The Tumbler control is used with one or more TumblerColumn items, which
- define the content of each column:
- \code
- Tumbler {
- TumblerColumn {
- model: 5
- }
- TumblerColumn {
- model: [0, 1, 2, 3, 4]
- }
- TumblerColumn {
- model: ["A", "B", "C", "D", "E"]
- }
- }
- \endcode
- You can also use a traditional model with roles:
- \code
- Rectangle {
- width: 220
- height: 350
- color: "#494d53"
- ListModel {
- id: listModel
- ListElement {
- foo: "A"
- bar: "B"
- baz: "C"
- }
- ListElement {
- foo: "A"
- bar: "B"
- baz: "C"
- }
- ListElement {
- foo: "A"
- bar: "B"
- baz: "C"
- }
- }
- Tumbler {
- anchors.centerIn: parent
- TumblerColumn {
- model: listModel
- role: "foo"
- }
- TumblerColumn {
- model: listModel
- role: "bar"
- }
- TumblerColumn {
- model: listModel
- role: "baz"
- }
- }
- }
- \endcode
- \section1 Limitations
- For technical reasons, the model count must be equal to or greater than
- \l {TumblerStyle::}{visibleItemCount}
- plus one. The
- \l {TumblerStyle::}{visibleItemCount}
- must also be an odd number.
- You can create a custom appearance for a Tumbler by assigning a
- \l {TumblerStyle}. To style
- individual columns, use the \l {TumblerColumn::delegate}{delegate} and
- \l {TumblerColumn::highlight}{highlight} properties of TumblerColumn.
- */
- Control {
- id: tumbler
- /*
- \qmlproperty Component Tumbler::style
- The style Component for this control.
- */
- style: Settings.styleComponent(Settings.style, "TumblerStyle.qml", tumbler)
- ListModel {
- id: columnModel
- }
- /*!
- \qmlproperty int Tumbler::columnCount
- The number of columns in the Tumbler.
- */
- readonly property alias columnCount: columnModel.count
- /*! \internal */
- function __isValidColumnIndex(index) {
- return index >= 0 && index < columnCount/* && columnRepeater.children.length === columnCount*/;
- }
- /*! \internal */
- function __isValidColumnAndItemIndex(columnIndex, itemIndex) {
- return __isValidColumnIndex(columnIndex) && itemIndex >= 0 && itemIndex < __viewAt(columnIndex).count;
- }
- /*!
- \qmlmethod int Tumbler::currentIndexAt(int columnIndex)
- Returns the current index of the column at \a columnIndex, or \c null
- if \a columnIndex is invalid.
- */
- function currentIndexAt(columnIndex) {
- if (!__isValidColumnIndex(columnIndex))
- return -1;
- return columnModel.get(columnIndex).columnObject.currentIndex;
- }
- /*!
- \qmlmethod void Tumbler::setCurrentIndexAt(int columnIndex, int itemIndex, int interval)
- Sets the current index of the column at \a columnIndex to \a itemIndex. The animation
- length can be set with \a interval, which defaults to \c 0.
- Does nothing if \a columnIndex or \a itemIndex are invalid.
- */
- function setCurrentIndexAt(columnIndex, itemIndex, interval) {
- if (!__isValidColumnAndItemIndex(columnIndex, itemIndex))
- return;
- var view = columnRepeater.itemAt(columnIndex).view;
- if (view.currentIndex !== itemIndex) {
- view.highlightMoveDuration = typeof interval !== 'undefined' ? interval : 0;
- view.currentIndex = itemIndex;
- view.highlightMoveDuration = Qt.binding(function(){ return __highlightMoveDuration; });
- }
- }
- /*!
- \qmlmethod TumblerColumn Tumbler::getColumn(int columnIndex)
- Returns the column at \a columnIndex or \c null if the index is
- invalid.
- */
- function getColumn(columnIndex) {
- if (!__isValidColumnIndex(columnIndex))
- return null;
- return columnModel.get(columnIndex).columnObject;
- }
- /*!
- \qmlmethod TumblerColumn Tumbler::addColumn(TumblerColumn column)
- Adds a \a column and returns the added column.
- The \a column argument can be an instance of TumblerColumn,
- or a \l Component. The component has to contain a TumblerColumn.
- Otherwise \c null is returned.
- */
- function addColumn(column) {
- return insertColumn(columnCount, column);
- }
- /*!
- \qmlmethod TumblerColumn Tumbler::insertColumn(int index, TumblerColumn column)
- Inserts a \a column at the given \a index and returns the inserted column.
- The \a column argument can be an instance of TumblerColumn,
- or a \l Component. The component has to contain a TumblerColumn.
- Otherwise, \c null is returned.
- */
- function insertColumn(index, column) {
- var object = column;
- if (typeof column["createObject"] === "function") {
- object = column.createObject(root);
- } else if (object.__tumbler) {
- console.warn("Tumbler::insertColumn(): you cannot add a column to multiple Tumblers")
- return null;
- }
- if (index >= 0 && index <= columnCount && object.accessibleRole === Accessible.ColumnHeader) {
- object.__tumbler = tumbler;
- object.__index = index;
- columnModel.insert(index, { columnObject: object });
- return object;
- }
- if (object !== column)
- object.destroy();
- console.warn("Tumbler::insertColumn(): invalid argument");
- return null;
- }
- /*
- Try making one selection bar by invisible highlight item hack, so that bars go across separators
- */
- Component.onCompleted: {
- for (var i = 0; i < data.length; ++i) {
- var column = data[i];
- if (column.accessibleRole === Accessible.ColumnHeader)
- addColumn(column);
- }
- }
- /*! \internal */
- readonly property alias __columnRow: columnRow
- /*! \internal */
- property int __highlightMoveDuration: 300
- /*! \internal */
- function __viewAt(index) {
- if (!__isValidColumnIndex(index))
- return null;
- return columnRepeater.itemAt(index).view;
- }
- /*! \internal */
- readonly property alias __movementDelayTimer: movementDelayTimer
- // When the up/down arrow keys are held down on a PathView,
- // the movement of the items is limited to the highlightMoveDuration,
- // but there is no built-in guard against trying to move the items at
- // the speed of the auto-repeat key presses. This results in sluggish
- // movement, so we enforce a delay with a timer to avoid this.
- Timer {
- id: movementDelayTimer
- interval: __highlightMoveDuration
- }
- Loader {
- id: backgroundLoader
- sourceComponent: __style.background
- anchors.fill: columnRow
- }
- Loader {
- id: frameLoader
- sourceComponent: __style.frame
- anchors.fill: columnRow
- anchors.leftMargin: -__style.padding.left
- anchors.rightMargin: -__style.padding.right
- anchors.topMargin: -__style.padding.top
- anchors.bottomMargin: -__style.padding.bottom
- }
- Row {
- id: columnRow
- x: __style.padding.left
- y: __style.padding.top
- Repeater {
- id: columnRepeater
- model: columnModel
- delegate: Item {
- id: columnItem
- width: columnPathView.width + separatorDelegateLoader.width
- height: columnPathView.height
- readonly property int __columnIndex: index
- // For index-related functions and tests.
- readonly property alias view: columnPathView
- readonly property alias separator: separatorDelegateLoader.item
- PathView {
- id: columnPathView
- width: columnObject.width
- height: tumbler.height - tumbler.__style.padding.top - tumbler.__style.padding.bottom
- visible: columnObject.visible
- clip: true
- Qml.Binding {
- target: columnObject
- property: "__currentIndex"
- value: columnPathView.currentIndex
- restoreMode: Binding.RestoreBinding
- }
- // We add one here so that the delegate's don't just appear in the view instantly,
- // but rather come from the top/bottom. To account for this adjustment elsewhere,
- // we extend the path height by half an item's height at the top and bottom.
- pathItemCount: tumbler.__style.visibleItemCount + 1
- preferredHighlightBegin: 0.5
- preferredHighlightEnd: 0.5
- highlightMoveDuration: tumbler.__highlightMoveDuration
- highlight: Loader {
- id: highlightLoader
- objectName: "highlightLoader"
- sourceComponent: columnObject.highlight ? columnObject.highlight : __style.highlight
- width: columnPathView.width
- readonly property int __index: index
- property QtObject styleData: QtObject {
- readonly property alias index: highlightLoader.__index
- readonly property int column: columnItem.__columnIndex
- readonly property bool activeFocus: columnPathView.activeFocus
- }
- }
- dragMargin: width / 2
- activeFocusOnTab: true
- Keys.onDownPressed: {
- if (!movementDelayTimer.running) {
- columnPathView.incrementCurrentIndex();
- movementDelayTimer.start();
- }
- }
- Keys.onUpPressed: {
- if (!movementDelayTimer.running) {
- columnPathView.decrementCurrentIndex();
- movementDelayTimer.start();
- }
- }
- path: Path {
- startX: columnPathView.width / 2
- startY: -tumbler.__style.__delegateHeight / 2
- PathLine {
- x: columnPathView.width / 2
- y: columnPathView.pathItemCount * tumbler.__style.__delegateHeight - tumbler.__style.__delegateHeight / 2
- }
- }
- model: columnObject.model
- delegate: Item {
- id: delegateRootItem
- property var itemModel: model
- implicitWidth: itemDelegateLoader.width
- implicitHeight: itemDelegateLoader.height
- Loader {
- id: itemDelegateLoader
- sourceComponent: columnObject.delegate ? columnObject.delegate : __style.delegate
- width: columnObject.width
- onHeightChanged: tumbler.__style.__delegateHeight = height;
- property var model: itemModel
- readonly property var __modelData: modelData
- readonly property int __columnDelegateIndex: index
- property QtObject styleData: QtObject {
- readonly property var modelData: itemDelegateLoader.__modelData
- readonly property alias index: itemDelegateLoader.__columnDelegateIndex
- readonly property int column: columnItem.__columnIndex
- readonly property bool activeFocus: columnPathView.activeFocus
- readonly property real displacement: {
- var count = delegateRootItem.PathView.view.count;
- var offset = delegateRootItem.PathView.view.offset;
- var d = count - index - offset;
- var halfVisibleItems = Math.floor(tumbler.__style.visibleItemCount / 2) + 1;
- if (d > halfVisibleItems)
- d -= count;
- else if (d < -halfVisibleItems)
- d += count;
- return d;
- }
- readonly property bool current: delegateRootItem.PathView.isCurrentItem
- readonly property string role: columnObject.role
- readonly property var value: (itemModel && itemModel.hasOwnProperty(role))
- ? itemModel[role] // Qml ListModel and QAbstractItemModel
- : modelData && modelData.hasOwnProperty(role)
- ? modelData[role] // QObjectList/QObject
- : modelData != undefined ? modelData : "" // Models without role
- }
- }
- }
- }
- Loader {
- anchors.fill: columnPathView
- sourceComponent: columnObject.columnForeground ? columnObject.columnForeground : __style.columnForeground
- property QtObject styleData: QtObject {
- readonly property int column: columnItem.__columnIndex
- readonly property bool activeFocus: columnPathView.activeFocus
- }
- }
- Loader {
- id: separatorDelegateLoader
- objectName: "separatorDelegateLoader"
- sourceComponent: __style.separator
- // Don't need a separator after the last delegate.
- active: __columnIndex < tumbler.columnCount - 1
- anchors.left: columnPathView.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- visible: columnObject.visible
- // Use the width of the first separator to help us
- // determine the default separator width.
- onWidthChanged: {
- if (__columnIndex == 0) {
- tumbler.__style.__separatorWidth = width;
- }
- }
- property QtObject styleData: QtObject {
- readonly property int index: __columnIndex
- }
- }
- }
- }
- }
- Loader {
- id: foregroundLoader
- sourceComponent: __style.foreground
- anchors.fill: backgroundLoader
- }
- }
|