on xenial help sign in
Simplify autoscroller
356c4bbJosh Arenson
qml/Dash/ScopesList.qml+106−39
@@ -22,6 +22,8 @@ import "../Components"
22Item {
22Item {
23 id: root
23 id: root
24
24
25+ property alias scopesListFlickable: scopesListFlickable
26+
25 // Properties set by parent
27 // Properties set by parent
26 property var scope: null
28 property var scope: null
27
29
@@ -35,11 +37,84 @@ Item {
35 signal requestFavoriteMoveTo(string scopeId, int index)
37 signal requestFavoriteMoveTo(string scopeId, int index)
36 signal requestRestore(string scopeId)
38 signal requestRestore(string scopeId)
37
39
38− state: "browse"
40+ Item {
41+ id: autoscroller
42+
43+ property bool dragging: false
44+ property var dragItem: new Object();
45+
46+ readonly property bool fuzzyAtYEnd: root.scopesListFlickable.contentY >=
47+ (root.scopesListFlickable.contentHeight - root.scopesListFlickable.height) - (autoscroller.dragItem.height)
48+
49+ readonly property real bottomBoundary: {
50+ (
51+ root.scopesListFlickable.visibleArea.heightRatio *
52+ root.scopesListFlickable.contentHeight
53+ )
54+ - (1.5 * dragItem.height)
55+ + root.scopesListFlickable.contentY
56+ }
57+
58+ readonly property int delayMs: 32
59+ readonly property real topBoundary: root.scopesListFlickable.contentY + (.5 * dragItem.height)
60+
61+ visible: false
62+ readonly property real maxStep: units.dp(10)
63+ function stepSize(scrollingUp) {
64+ var delta, step;
65+ if (scrollingUp) {
66+ delta = dragItem.y - topBoundary;
67+ delta /= (1.5 * dragItem.height);
68+ } else {
69+ delta = dragItem.y - bottomBoundary;
70+ delta /= (1.5 * dragItem.height);
71+ }
72+
73+ step = Math.abs(delta) * autoscroller.maxStep
74+ return Math.ceil(step);
75+ }
76+
39
77
40− property var scopeStyle: ScopeStyle {
78+ Timer {
79+ interval: autoscroller.delayMs
80+ running: autoscroller.dragging &&
81+ autoscroller.dragItem.y < autoscroller.topBoundary &&
82+ !root.scopesListFlickable.atYBeginning
83+ repeat: true
84+ onTriggered: {
85+ root.scopesListFlickable.contentY -= autoscroller.stepSize(true);
86+ autoscroller.dragItem.y -= autoscroller.stepSize(true);
87+ }
88+
89+ }
90+
91+ Timer {
92+ interval: autoscroller.delayMs
93+ running: autoscroller.dragging &&
94+ autoscroller.dragItem.y >= autoscroller.bottomBoundary &&
95+ !autoscroller.fuzzyAtYEnd
96+ repeat: true
97+ onTriggered: {
98+ root.scopesListFlickable.contentY += autoscroller.stepSize(false);
99+ autoscroller.dragItem.y += autoscroller.stepSize(false);
100+ }
101+ }
41 }
102 }
42
103
104+ function autoscroll(dragging, dragItem) {
105+ if (dragging) {
106+ autoscroller.dragItem = dragItem
107+ autoscroller.dragging = true;
108+ } else {
109+ autoscroller.dragItem = null;
110+ autoscroller.dragging = false
111+ }
112+ }
113+
114+ state: "browse"
115+
116+ property var scopeStyle: ScopeStyle {}
117+
43 onStateChanged: {
118 onStateChanged: {
44 if (state == "edit") {
119 if (state == "edit") {
45 // As per design entering edit mode clears the possible existing search
120 // As per design entering edit mode clears the possible existing search
@@ -73,7 +148,8 @@ Item {
73 z: 1
148 z: 1
74 }
149 }
75
150
76− Flickable {
151+ ListView {
152+ id: scopesListFlickable
77 objectName: "scopesListFlickable"
153 objectName: "scopesListFlickable"
78 anchors {
154 anchors {
79 top: header.bottom
155 top: header.bottom
@@ -82,43 +158,34 @@ Item {
82 right: parent.right
158 right: parent.right
83 }
159 }
84 clip: true
160 clip: true
85− contentWidth: root.width
86− contentHeight: column.height
87− onContentHeightChanged: returnToBounds();
88− Column {
89− id: column
90− Repeater {
91− model: scope ? scope.categories : null
161+ model: scope ? scope.categories : null
92−
93− delegate: Loader {
162+ delegate: Loader {
94− asynchronous: true
163+ asynchronous: true
95− width: root.width
164+ width: root.width
96− active: results.count > 0
165+ active: results.count > 0
97− visible: active
166+ visible: active
98− sourceComponent: ScopesListCategory {
167+ sourceComponent: ScopesListCategory {
99− objectName: "scopesListCategory" + categoryId
168+ objectName: "scopesListCategory" + categoryId
100−
169+
101− model: results
170+ model: results
102−
171+
103− title: {
172+ title: {
104− if (isFavoritesFeed) return i18n.tr("Home");
173+ if (isFavoritesFeed) return i18n.tr("Home");
105− else if (isAlsoInstalled) return i18n.tr("Also installed");
174+ else if (isAlsoInstalled) return i18n.tr("Also installed");
106− else return name;
175+ else return name;
107− }
108−
109− editMode: root.state == "edit"
110−
111− scopeStyle: root.scopeStyle
112− isFavoritesFeed: categoryId == "favorites"
113− isAlsoInstalled: categoryId == "other"
114−
115− onRequestFavorite: root.requestFavorite(scopeId, favorite);
116− onRequestEditMode: root.state = "edit";
117− onRequestScopeMoveTo: root.requestFavoriteMoveTo(scopeId, index);
118− onRequestActivate: root.scope.activate(result, categoryId);
119− onRequestRestore: root.requestRestore(scopeId);
120 }
176 }
121− }
177+
178+ editMode: root.state == "edit"
179+ scopeStyle: root.scopeStyle
180+ isFavoritesFeed: categoryId == "favorites"
181+ isAlsoInstalled: categoryId == "other"
182+
183+ onItemDragging: autoscroll(dragging, dragItem);
184+ onRequestFavorite: root.requestFavorite(scopeId, favorite);
185+ onRequestEditMode: root.state = "edit";
186+ onRequestScopeMoveTo: root.requestFavoriteMoveTo(scopeId, index);
187+ onRequestActivate: root.scope.activate(result, categoryId);
188+ onRequestRestore: root.requestRestore(scopeId);
122 }
189 }
123 }
190 }
124 }
191 }
qml/Dash/ScopesListCategory.qml+3
@@ -32,6 +32,7 @@ Item {
32
32
33 visible: !editMode || isFavoritesFeed
33 visible: !editMode || isFavoritesFeed
34
34
35+ signal itemDragging(bool dragging, var dragItem)
35 signal requestFavorite(string scopeId, bool favorite)
36 signal requestFavorite(string scopeId, bool favorite)
36 signal requestEditMode()
37 signal requestEditMode()
37 signal requestScopeMoveTo(string scopeId, int index)
38 signal requestScopeMoveTo(string scopeId, int index)
@@ -109,6 +110,7 @@ Item {
109 onRequestFavorite: root.requestFavorite(model.scopeId, favorite);
110 onRequestFavorite: root.requestFavorite(model.scopeId, favorite);
110 onHandlePressed: {
111 onHandlePressed: {
111 if (editMode) {
112 if (editMode) {
113+ root.itemDragging(true, dragItem);
112 handle.drag.target = dragItem;
114 handle.drag.target = dragItem;
113 handle.drag.maximumX = units.gu(1);
115 handle.drag.maximumX = units.gu(1);
114 handle.drag.minimumX = units.gu(1);
116 handle.drag.minimumX = units.gu(1);
@@ -126,6 +128,7 @@ Item {
126 }
128 }
127 }
129 }
128 onHandleReleased: {
130 onHandleReleased: {
131+ root.itemDragging(false, dragItem);
129 if (dragItem.visible) {
132 if (dragItem.visible) {
130 handle.drag.target = undefined;
133 handle.drag.target = undefined;
131 dragItem.visible = false;
134 dragItem.visible = false;
tests/mocks/Unity/fake_scopes.cpp+7
@@ -73,6 +73,13 @@ void Scopes::updateScopes()
73 addScope(longNavigationScope);
73 addScope(longNavigationScope);
74
74
75 addScope(new Scope("NullPreviewScope", "NPS", false, this, 1, true));
75 addScope(new Scope("NullPreviewScope", "NPS", false, this, 1, true));
76+
77+ // Add enough scopes to test autoscrolling on the scopes managment page
78+ for (int i = 0; i < 20; i++) {
79+ addScope(new Scope("UselessScope" + QString::number(i),
80+ "US" + QString::number(i), true, this));
81+ }
82+
76 m_scopesOverview = new ScopesOverview(this);
83 m_scopesOverview = new ScopesOverview(this);
77
84
78 if (!m_loaded) {
85 if (!m_loaded) {