on xenial help sign in
DragHandle: add stretch and hintDisplacement properties.

Approved by PS Jenkins bot, Nick Dedekind, Albert Astals Cid.

commit8ba498c5811eebf4a43f099a495a623672e6e4ae authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateMon 1 Jul 2013, 20:03 (13 years ago) parente5bf7c6a7f600f
7 files changed+353−67
Components/DragHandle.qml+124−57 tests/qmltests/Components/tst_DragHandle.cpp+100 tests/qmltests/Components/tst_DragHandle.qml+32 tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+28−5 tests/qmltests/Components/tst_DragHandle/TestButton.qml+41 tests/qmltests/Components/tst_DragHandle/UnityLogo.png tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+28−5
Components/DragHandle.qml+124−57
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Ubuntu.Gestures 0.1
20+import "Math.js" as MathLocal
2021
2122/*
2223 Put a DragHandle inside a Showable to enable the user to drag it from that handle.
@@ -45,19 +46,122 @@ import Ubuntu.Gestures 0.1
4546 */
4647EdgeDragArea {
4748 id: dragArea
49+ objectName: "dragHandle"
50+
51+ property bool stretch: false
52+
53+ property alias autoCompleteDragThreshold: dragEvaluator.dragThreshold
4854
4955 // How far you can drag
50− property real maxTotalDragDistance: Direction.isHorizontal(direction) ? parent.width : parent.height
56+ property real maxTotalDragDistance: {
57+ if (stretch) {
58+ 0; // not enough context information to set a sensible default
59+ } else {
60+ Direction.isHorizontal(direction) ? parent.width : parent.height;
61+ }
62+ }
63+
64+ property real hintDisplacement: 0
65+ SmoothedAnimation {
66+ id: hintingAnimation
67+ target: hintingAnimation
68+ property: "targetValue"
69+ duration: 150
70+ velocity: -1
71+ to: Direction.isPositive(direction) ? d.startValue + hintDisplacement
72+ : d.startValue - hintDisplacement
73+ property real targetValue
74+ onTargetValueChanged: {
75+ if (!running) {
76+ return;
77+ }
78+
79+ if (Direction.isPositive(direction)) {
80+ if (parent[d.targetProp] < targetValue) {
81+ parent[d.targetProp] = targetValue;
82+ }
83+ } else {
84+ if (parent[d.targetProp] > targetValue) {
85+ parent[d.targetProp] = targetValue;
86+ }
87+ }
88+ }
89+ }
90+
91+ // Private stuff
92+ QtObject {
93+ id: d
94+ property var previousStatus: undefined
95+ property real startValue
96+ property real minValue: Direction.isPositive(direction) ? startValue
97+ : startValue - maxTotalDragDistance
98+ property real maxValue: Direction.isPositive(direction) ? startValue + maxTotalDragDistance
99+ : startValue
100+
101+ property var dragParent: dragArea.parent
102+
103+ // The property of DragHandle's parent that will be modified
104+ property string targetProp: {
105+ if (stretch) {
106+ Direction.isHorizontal(direction) ? "width" : "height";
107+ } else {
108+ Direction.isHorizontal(direction) ? "x" : "y";
109+ }
110+ }
111+
112+ function limitMovement(step) {
113+ var targetValue = MathLocal.clamp(dragParent[targetProp] + step, minValue, maxValue);
114+ var step = targetValue - dragParent[targetProp];
115+
116+ if (hintDisplacement == 0) {
117+ return step;
118+ }
119+
120+ // we should not go behind hintingAnimation's current value
121+ if (Direction.isPositive(direction)) {
122+ if (dragParent[targetProp] + step < hintingAnimation.targetValue) {
123+ step = hintingAnimation.targetValue - dragParent[targetProp];
124+ }
125+ } else {
126+ if (dragParent[targetProp] + step > hintingAnimation.targetValue) {
127+ step = hintingAnimation.targetValue - dragParent[targetProp];
128+ }
129+ }
130+
131+ return step;
132+ }
133+
134+ function onFinishedRecognizedGesture() {
135+ if (dragEvaluator.shouldAutoComplete()) {
136+ completeDrag();
137+ } else {
138+ rollbackDrag();
139+ }
140+ }
51141
52− property var __previousStatus
53− property real __totalDragDistance: 0
142+ function completeDrag() {
143+ if (dragParent.shown) {
144+ dragParent.hide();
145+ } else {
146+ dragParent.show();
147+ }
148+ }
149+
150+ function rollbackDrag() {
151+ if (dragParent.shown) {
152+ dragParent.show();
153+ } else {
154+ dragParent.hide();
155+ }
156+ }
157+ }
54158
55159 property alias edgeDragEvaluator: dragEvaluator
56160
57161 EdgeDragEvaluator {
58162 objectName: "edgeDragEvaluator"
59163 id: dragEvaluator
60− trackedPosition: Direction.isHorizontal(direction) ? parent.x + touchX : parent.y + touchY
164+ trackedPosition: sceneDistance
61165 maxDragDistance: maxTotalDragDistance
62166 direction: dragArea.direction
63167 }
@@ -67,75 +171,38 @@ EdgeDragArea {
67171 // don't go the whole distance in order to smooth out the movement
68172 var step = distance * 0.3;
69173
70− step = __limitMovement(step);
174+ step = d.limitMovement(step);
71175
72− __totalDragDistance += step;
73−
74− if (Direction.isHorizontal(direction)) {
75− parent.x += step;
76− } else {
77− parent.y += step;
78− }
176+ parent[d.targetProp] += step;
79177 }
80178 }
81179
82−
83− function __limitMovement(step) {
84− if (Direction.isPositive(direction)) {
85− if (__totalDragDistance + step > maxTotalDragDistance) {
86− step = maxTotalDragDistance - __totalDragDistance;
87− } else if (__totalDragDistance + step < 0) {
88− step = 0 - __totalDragDistance;
180+ onDraggingChanged: {
181+ if (dragging) {
182+ if (hintDisplacement > 0) {
183+ hintingAnimation.targetValue = d.startValue;
184+ hintingAnimation.start();
89185 }
90186 } else {
91− if (__totalDragDistance + step < -maxTotalDragDistance) {
92− step = -maxTotalDragDistance - __totalDragDistance;
93− } else if (__totalDragDistance + step > 0) {
94− step = 0 - __totalDragDistance;
95− }
187+ hintingAnimation.stop();
96188 }
97−
98− return step;
99189 }
100190
101191 onStatusChanged: {
102192 if (status === DirectionalDragArea.WaitingForTouch) {
103− if (__previousStatus === DirectionalDragArea.Recognized) {
104− __onFinishedRecognizedGesture();
193+ if (d.previousStatus === DirectionalDragArea.Recognized) {
194+ d.onFinishedRecognizedGesture();
105195 }
106− __totalDragDistance = 0;
196+ d.startValue = parent[d.targetProp];
107197 }
108− else if (status === DirectionalDragArea.Undecided) {
198+
199+ if (d.previousStatus === DirectionalDragArea.WaitingForTouch ||
200+ d.previousStatus === undefined) {
109201 dragEvaluator.reset();
202+ d.startValue = parent[d.targetProp];
110203 }
111− else if (status === DirectionalDragArea.Recognized) {
112− if (__previousStatus === DirectionalDragArea.WaitingForTouch)
113− dragEvaluator.reset();
114− }
115− __previousStatus = status;
116− }
117204
118− function __onFinishedRecognizedGesture() {
119− if (dragEvaluator.shouldAutoComplete()) {
120− __completeDrag();
121− } else {
122− __rollbackDrag();
123− }
205+ d.previousStatus = status;
124206 }
125207
126− function __completeDrag() {
127− if (parent.shown) {
128− parent.hide();
129− } else {
130− parent.show();
131− }
132− }
133−
134− function __rollbackDrag() {
135− if (parent.shown) {
136− parent.show();
137− } else {
138− parent.hide();
139− }
140− }
141208}
tests/qmltests/Components/tst_DragHandle.cpp+100
@@ -59,6 +59,9 @@ private Q_SLOTS:
5959
6060 void dragThreshold_horizontal();
6161 void dragThreshold_vertical();
62+ void stretch_horizontal();
63+ void stretch_vertical();
64+ void hintingAnimation();
6265
6366private:
6467 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
@@ -290,6 +293,103 @@ void tst_DragHandle::dragThreshold_vertical()
290293 QCOMPARE(parentItem->property("shown").toBool(), false);
291294}
292295
296+/*
297+ Checks that when the stretch property is true, dragging the DragHandle increases
298+ the width or height (depending on its direction) of its parent Showable
299+ */
300+void tst_DragHandle::stretch_horizontal()
301+{
302+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
303+ qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
304+ QQuickItem *parentItem = dragHandle->parentItem();
305+
306+ // enable strech mode
307+ m_view->rootObject()->setProperty("stretch", QVariant(true));
308+
309+ QCOMPARE(parentItem->width(), 0.0);
310+
311+ // flick all the way
312+ flickAndHold(dragHandle, totalDragDistance);
313+
314+ // should keep going until completion
315+ // Parent item should now have its full height
316+ tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
317+ QCOMPARE(parentItem->property("shown").toBool(), true);
318+
319+ dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
320+
321+ // flick all the way
322+ flickAndHold(dragHandle, totalDragDistance);
323+
324+ // should keep going until completion
325+ // Parent item should now have its full height
326+ tryCompare([&](){ return parentItem->width(); }, 0.0);
327+ QCOMPARE(parentItem->property("shown").toBool(), false);
328+}
329+
330+void tst_DragHandle::stretch_vertical()
331+{
332+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
333+ qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
334+ QQuickItem *parentItem = dragHandle->parentItem();
335+
336+ // enable strech mode
337+ m_view->rootObject()->setProperty("stretch", QVariant(true));
338+
339+ QCOMPARE(parentItem->height(), 0.0);
340+
341+ // flick all the way
342+ flickAndHold(dragHandle, totalDragDistance);
343+
344+ // should keep going until completion
345+ // Parent item should now have its full height
346+ tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
347+ QCOMPARE(parentItem->property("shown").toBool(), true);
348+
349+ dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
350+
351+ // flick all the way
352+ flickAndHold(dragHandle, totalDragDistance);
353+
354+ // should keep going until completion
355+ // Parent item should now have its full height
356+ tryCompare([&](){ return parentItem->height(); }, 0.0);
357+ QCOMPARE(parentItem->property("shown").toBool(), false);
358+}
359+
360+/*
361+ Set DragHandle.hintDisplacement to a value bigger than zero.
362+ Then lay a finger on the DragHandle.
363+ The expected behavior is that it will move or strech its parent Showable
364+ by hintDisplacement pixels.
365+ */
366+void tst_DragHandle::hintingAnimation()
367+{
368+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
369+ QQuickItem *parentItem = dragHandle->parentItem();
370+ qreal hintDisplacement = 100.0;
371+
372+ // enable hinting animations and stretch mode
373+ m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
374+ m_view->rootObject()->setProperty("stretch", QVariant(true));
375+
376+ QCOMPARE(parentItem->height(), 0.0);
377+
378+ QPointF initialTouchPos = dragHandle->mapToScene(
379+ QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
380+ QPointF touchPoint = initialTouchPos;
381+
382+ // Pressing causes the Showable to be stretched by hintDisplacement pixels
383+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
384+ tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
385+
386+ // Releasing causes the Showable to shrink back to 0 pixels.
387+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
388+ tryCompare([&](){ return parentItem->height(); }, 0.0);
389+
390+ QCOMPARE(parentItem->property("shown").toBool(), false);
391+}
392+
293393QTEST_MAIN(tst_DragHandle)
294394
295395#include "tst_DragHandle.moc"
tests/qmltests/Components/tst_DragHandle.qml+32
@@ -34,12 +34,19 @@ Item {
3434
3535 property var dragHandle
3636
37+ property bool stretch: false
38+ property real hintDisplacement: 0
39+
3740 VerticalShowable {
3841 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
42+ stretch: root.stretch
43+ hintDisplacement: root.hintDisplacement
3944 }
4045
4146 HorizontalShowable {
4247 onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
48+ stretch: root.stretch
49+ hintDisplacement: root.hintDisplacement
4350 }
4451
4552 // Visually mark drag threshold
@@ -111,4 +118,29 @@ Item {
111118 }
112119 }
113120 }
121+
122+ TestButton {
123+ id: stretchButton
124+ anchors.bottom: parent.bottom
125+ anchors.left: parent.left
126+ anchors.margins: units.gu(1)
127+
128+ text: root.stretch ? "stretch" : "move"
129+ onClicked: { root.stretch = !root.stretch; }
130+ }
131+
132+ TestButton {
133+ anchors.bottom: parent.bottom
134+ anchors.left: stretchButton.right
135+ anchors.margins: units.gu(1)
136+
137+ text: root.hintDisplacement > 0 ? "hint" : "no hint"
138+ onClicked: {
139+ if (root.hintDisplacement > 0) {
140+ root.hintDisplacement = 0;
141+ } else {
142+ root.hintDisplacement = units.gu(6);
143+ }
144+ }
145+ }
114146}
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+28−5
@@ -21,18 +21,35 @@ import Ubuntu.Gestures 0.1
2121
2222Showable {
2323 id: showable
24− x: -width
25− width: parent.width
24+ x: stretch ? 0 : -width
25+ width: stretch ? 0 : parent.width
2626 height: parent.height
2727
28+ property bool stretch
29+ property real hintDisplacement
30+
31+ onStretchChanged: {
32+ if (stretch) {
33+ x = 0;
34+ width = 0;
35+ } else {
36+ x = -parent.width;
37+ width = parent.width;
38+ }
39+ }
40+
2841 shown: false
2942
3043 signal dragHandleRecognizedGesture(var dragHandle)
3144
32− showAnimation: StandardAnimation { property: "x"; to: 0 }
33− hideAnimation: StandardAnimation { property: "x"; to: -width }
45+ property string animatedProp: stretch ? "width" : "x"
46+ property real propValueWhenShown: stretch ? parent.width : 0
47+ property real propValueWhenHidden: stretch ? 0 : -width
48+
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
3451
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
3653
3754 DragHandle {
3855 objectName: "leftwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
4461 width: units.gu(2)
4562
4663 direction: Direction.Leftwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.width
66+ hintDisplacement: showable.hintDisplacement
4767
4868 onStatusChanged: {
4969 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
6484 width: units.gu(2)
6585
6686 direction: Direction.Rightwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.width
89+ hintDisplacement: showable.hintDisplacement
6790
6891 onStatusChanged: {
6992 if (status === DirectionalDragArea.Recognized) {
tests/qmltests/Components/tst_DragHandle/TestButton.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+Rectangle {
21+ id: rect
22+ width: childrenRect.width
23+ height: childrenRect.height
24+
25+ property alias text: textComponent.text
26+
27+ signal clicked
28+
29+ color: "yellow"
30+ Text {
31+ id: textComponent
32+ font.pixelSize: units.gu(2)
33+ text: root.stretch ? "stretch" : "move"
34+ MouseArea {
35+ anchors.fill: parent
36+ onClicked: {
37+ rect.clicked()
38+ }
39+ }
40+ }
41+}
tests/qmltests/Components/tst_DragHandle/UnityLogo.pngnew file

Binary file not shown.

tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+28−5
@@ -21,18 +21,35 @@ import Ubuntu.Gestures 0.1
2121
2222Showable {
2323 id: showable
24− y: -height
24+ y: stretch ? 0 : -height
2525 width: parent.width
26− height: parent.height
26+ height: stretch ? 0 : parent.height
27+
28+ property bool stretch
29+ property real hintDisplacement
30+
31+ onStretchChanged: {
32+ if (stretch) {
33+ y = 0;
34+ height = 0;
35+ } else {
36+ y = -parent.height;
37+ height = parent.height;
38+ }
39+ }
2740
2841 shown: false
2942
3043 signal dragHandleRecognizedGesture(var dragHandle)
3144
32− showAnimation: StandardAnimation { property: "y"; to: 0 }
33− hideAnimation: StandardAnimation { property: "y"; to: -height }
45+ property string animatedProp: stretch ? "height" : "y"
46+ property real propValueWhenShown: stretch ? parent.height : 0
47+ property real propValueWhenHidden: stretch ? 0 : -height
48+
49+ showAnimation: StandardAnimation { property: animatedProp; to: propValueWhenShown }
50+ hideAnimation: StandardAnimation { property: animatedProp; to: propValueWhenHidden }
3451
35− Rectangle { color: "blue"; anchors.fill: parent }
52+ Image { source: "UnityLogo.png"; anchors.fill: parent }
3653
3754 DragHandle {
3855 objectName: "upwardsDragHandle"
@@ -44,6 +61,9 @@ Showable {
4461 height: units.gu(2)
4562
4663 direction: Direction.Upwards
64+ stretch: showable.stretch
65+ maxTotalDragDistance: showable.parent.height
66+ hintDisplacement: showable.hintDisplacement
4767
4868 onStatusChanged: {
4969 if (status === DirectionalDragArea.Recognized) {
@@ -64,6 +84,9 @@ Showable {
6484 height: units.gu(2)
6585
6686 direction: Direction.Downwards
87+ stretch: showable.stretch
88+ maxTotalDragDistance: showable.parent.height
89+ hintDisplacement: showable.hintDisplacement
6790
6891 onStatusChanged: {
6992 if (status === DirectionalDragArea.Recognized) {