From 47a8b89bcc3b447bea7931feff27517a6b9337c4 Mon Sep 17 00:00:00 2001 From: J-P Nurmi <jpnurmi@digia.com> Date: Thu, 25 Apr 2013 18:50:26 +0200 Subject: [PATCH] Introduce support for movable tabs Change-Id: I87a6a582824363197f66f6b7f6f29aa603123458 Reviewed-by: Caroline Chao <caroline.chao@digia.com> Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com> --- .../quick/controls/gallery/content/Styles.qml | 1 + src/private/TabBar.qml | 167 +++++++++++++----- src/styles/Desktop/TabViewStyle.qml | 1 + src/styles/TabViewStyle.qml | 4 + tests/auto/controls/data/tst_tabview.qml | 5 +- 5 files changed, 127 insertions(+), 51 deletions(-) diff --git a/examples/quick/controls/gallery/content/Styles.qml b/examples/quick/controls/gallery/content/Styles.qml index f6892b7a0..73850ce5a 100644 --- a/examples/quick/controls/gallery/content/Styles.qml +++ b/examples/quick/controls/gallery/content/Styles.qml @@ -242,6 +242,7 @@ Item { tabOverlap: 16 tabsLeftPadding: 12 frameOverlap: 4 + tabsMovable: true frame: Rectangle { gradient: Gradient{ diff --git a/src/private/TabBar.qml b/src/private/TabBar.qml index e9214c740..ebaef02a3 100644 --- a/src/private/TabBar.qml +++ b/src/private/TabBar.qml @@ -69,6 +69,8 @@ FocusScope { property var style property var styleItem: tabView.__styleItem ? tabView.__styleItem : null + property bool tabsMovable: styleItem ? styleItem.tabsMovable : false + property int tabsAlignment: styleItem ? styleItem.tabsAlignment : Qt.AlignLeft property int tabOverlap: styleItem ? styleItem.tabOverlap : 0 @@ -84,11 +86,25 @@ FocusScope { return null; } - Row { + ListView { id: tabrow objectName: "tabrow" Accessible.role: Accessible.PageTabList spacing: -tabOverlap + orientation: Qt.Horizontal + interactive: false + focus: true + + width: contentItem ? contentItem.width : 0 + height: currentItem ? currentItem.height : 0 + + displaced: Transition { + NumberAnimation { + property: "x" + duration: 125 + easing.type: Easing.OutQuad + } + } states: [ State { @@ -110,60 +126,113 @@ FocusScope { } ] + model: tabView.__tabs - Repeater { - id: repeater - objectName: "repeater" + delegate: MouseArea { + id: tabitem + objectName: "mousearea" + hoverEnabled: true focus: true - model: tabView.__tabs - - delegate: Item { - id: tabitem - focus: true - - property int tabindex: index - property bool selected : tabView.currentIndex === index - property bool hover: mousearea.containsMouse - property string title: modelData.title - property bool nextSelected: tabView.currentIndex === index + 1 - property bool previousSelected: tabView.currentIndex === index - 1 - - z: selected ? 1 : -index - implicitWidth: Math.min(tabloader.implicitWidth, tabbar.width/repeater.count) + 1 - implicitHeight: tabloader.implicitHeight - - Loader { - id: tabloader - - sourceComponent: loader.item ? loader.item.tab : null - anchors.fill: parent - - property Item control: tabView - property int index: tabindex - - property QtObject tab: QtObject { - readonly property alias index: tabitem.tabindex - readonly property alias selected: tabitem.selected - readonly property alias title: tabitem.title - readonly property alias nextSelected: tabitem.nextSelected - readonly property alias previsousSelected: tabitem.previousSelected - readonly property alias hovered: tabitem.hover - readonly property bool activeFocus: tabbar.activeFocus - } + + drag.target: tabsMovable ? tabloader : null + drag.axis: Drag.XAxis + drag.minimumX: drag.active ? 0 : -Number.MAX_VALUE + drag.maximumX: tabrow.width - tabitem.width + + property int tabindex: index + property bool selected : tabView.currentIndex === index + property bool hover: containsMouse + property string title: modelData.title + property bool nextSelected: tabView.currentIndex === index + 1 + property bool previousSelected: tabView.currentIndex === index - 1 + + z: selected ? 1 : -index + implicitWidth: Math.min(tabloader.implicitWidth, tabbar.width/tabrow.count) + 1 + implicitHeight: tabloader.implicitHeight + + onPressed: { + tabView.currentIndex = index; + tabbar.nextItemInFocusChain(true).forceActiveFocus(); + } + + Loader { + id: tabloader + + property Item control: tabView + property int index: tabindex + + property QtObject tab: QtObject { + readonly property alias index: tabitem.tabindex + readonly property alias selected: tabitem.selected + readonly property alias title: tabitem.title + readonly property alias nextSelected: tabitem.nextSelected + readonly property alias previsousSelected: tabitem.previousSelected + readonly property alias hovered: tabitem.hover + readonly property bool activeFocus: tabbar.activeFocus } - MouseArea { - id: mousearea - objectName: "mousearea" - anchors.fill: parent - hoverEnabled: true - onPressed: { - tabView.currentIndex = index; - tabbar.nextItemInFocusChain(true).forceActiveFocus(); + sourceComponent: loader.item ? loader.item.tab : null + + Drag.keys: "application/x-tabbartab" + Drag.active: tabitem.drag.active + Drag.source: tabitem + + property real __prevX: 0 + property real __dragX: 0 + onXChanged: { + if (Drag.active) { + // keep track for the snap back animation + __dragX = tabitem.mapFromItem(tabrow, tabloader.x, 0).x + + // when moving to the left, the hot spot is the left edge and vice versa + Drag.hotSpot.x = x < __prevX ? 0 : width + __prevX = x } } - Accessible.role: Accessible.PageTab - Accessible.name: modelData.title + + width: tabitem.width + state: Drag.active ? "drag" : "" + + transitions: [ + Transition { + to: "drag" + PropertyAction { target: tabloader; property: "parent"; value: tabrow } + }, + Transition { + from: "drag" + SequentialAnimation { + PropertyAction { target: tabloader; property: "parent"; value: tabitem } + NumberAnimation { + target: tabloader + duration: 50 + easing.type: Easing.OutQuad + property: "x" + from: tabloader.__dragX + to: 0 + } + } + } + ] + } + + Accessible.role: Accessible.PageTab + Accessible.name: modelData.title + } + } + + DropArea { + anchors.fill: tabrow + keys: "application/x-tabbartab" + onPositionChanged: { + var source = drag.source + var target = tabrow.itemAt(drag.x, drag.y) + if (source && target && source !== target) { + source = source.drag.target + target = target.drag.target + var center = target.parent.x + target.width / 2 + if ((source.index > target.index && source.x < center) + || (source.index < target.index && source.x + source.width > center)) + tabView.moveTab(source.index, target.index) } } } diff --git a/src/styles/Desktop/TabViewStyle.qml b/src/styles/Desktop/TabViewStyle.qml index 830732bbe..bd3adb94f 100644 --- a/src/styles/Desktop/TabViewStyle.qml +++ b/src/styles/Desktop/TabViewStyle.qml @@ -44,6 +44,7 @@ import QtQuick.Controls.Styles 1.0 Style { id: root + property bool tabsMovable: false property int tabsLeftPadding: 0 property int tabsRightPadding: 0 property int tabsAlignment: __barstyle.styleHint("tabbaralignment") === "center" ? Qt.AlignHCenter : Qt.AlignLeft; diff --git a/src/styles/TabViewStyle.qml b/src/styles/TabViewStyle.qml index b08848917..b9666db23 100644 --- a/src/styles/TabViewStyle.qml +++ b/src/styles/TabViewStyle.qml @@ -83,6 +83,10 @@ Style { /*! The \l ScrollView attached to this style. */ readonly property TabView control: __control + /*! This property holds whether the user can move the tabs. + Tabs are not movable by default. */ + property bool tabsMovable: false + /*! This property holds the horizontal alignment of the tab buttons. Supported values are: \list diff --git a/tests/auto/controls/data/tst_tabview.qml b/tests/auto/controls/data/tst_tabview.qml index 43c42c99e..64e2fc445 100644 --- a/tests/auto/controls/data/tst_tabview.qml +++ b/tests/auto/controls/data/tst_tabview.qml @@ -274,6 +274,7 @@ TestCase { compare(tabView.count, 2) verify(tabView.tab1.status === Loader.Ready) verify(tabView.tab2.status === Loader.Ready) + waitForRendering(tabView) var column1 = getColumnItem(tabView.tab1, "column1") verify(column1 !== null) @@ -294,7 +295,7 @@ TestCase { var mouseareas = populateMouseAreaItems(tabrowItem) verify(mouseareas.length, 2) - var tab1 = mouseareas[0].parent + var tab1 = mouseareas[0] verify(tab1 !== null) //printGeometry(tab1) @@ -302,7 +303,7 @@ TestCase { mouseClick(tab1, tab1.width/2, tab1.height/2) verify(child1.activeFocus) - var tab2 = mouseareas[1].parent + var tab2 = mouseareas[1] verify(tab2 !== null) //printGeometry(tab2) -- GitLab