Changeset View
Changeset View
Standalone View
Standalone View
src/controls/templates/private/PageRow.qml
Show All 13 Lines | |||||
14 | * You should have received a copy of the GNU Library General Public | 14 | * You should have received a copy of the GNU Library General Public | ||
15 | * License along with this program; if not, write to the | 15 | * License along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | 16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | 18 | */ | ||
19 | 19 | | |||
20 | import QtQuick 2.5 | 20 | import QtQuick 2.5 | ||
21 | import QtQuick.Layouts 1.2 | 21 | import QtQuick.Layouts 1.2 | ||
22 | import QtQml.Models 2.2 | ||||
22 | import QtQuick.Templates 2.0 as T | 23 | import QtQuick.Templates 2.0 as T | ||
23 | import org.kde.kirigami 2.0 | 24 | import org.kde.kirigami 2.0 | ||
24 | 25 | | |||
25 | Item { | 26 | T.Control { | ||
26 | id: root | 27 | id: root | ||
27 | 28 | | |||
28 | //BEGIN PROPERTIES | 29 | //BEGIN PROPERTIES | ||
29 | /** | 30 | /** | ||
30 | * This property holds the number of items currently pushed onto the view | 31 | * This property holds the number of items currently pushed onto the view | ||
31 | */ | 32 | */ | ||
32 | readonly property alias depth: pagesLogic.count | 33 | readonly property alias depth: pagesLogic.count | ||
33 | 34 | | |||
34 | /** | 35 | /** | ||
35 | * The last Page in the Row | 36 | * The last Page in the Row | ||
36 | */ | 37 | */ | ||
37 | readonly property Item lastItem: pagesLogic.count ? pagesLogic.get(pagesLogic.count - 1).page : null | 38 | readonly property Item lastItem: pagesLogic.count ? pagesLogic.get(pagesLogic.count - 1).page : null | ||
38 | 39 | | |||
39 | /** | 40 | /** | ||
40 | * The currently visible Item | 41 | * The currently visible Item | ||
41 | */ | 42 | */ | ||
42 | readonly property Item currentItem: mainFlickable.currentItem | 43 | readonly property Item currentItem: mainView.currentItem.page | ||
43 | 44 | | |||
44 | /** | 45 | /** | ||
45 | * the index of the currently visible Item | 46 | * the index of the currently visible Item | ||
46 | */ | 47 | */ | ||
47 | property alias currentIndex: mainFlickable.currentIndex | 48 | property alias currentIndex: mainView.currentIndex | ||
48 | 49 | | |||
49 | /** | 50 | /** | ||
50 | * The initial item when this PageRow is created | 51 | * The initial item when this PageRow is created | ||
51 | */ | 52 | */ | ||
52 | property variant initialPage | 53 | property variant initialPage | ||
53 | 54 | | |||
54 | /** | 55 | /** | ||
55 | * The main flickable of this Row | 56 | * The main flickable of this Row | ||
56 | */ | 57 | */ | ||
57 | property alias contentItem: mainFlickable | 58 | // property alias contentItem: mainView | ||
59 | contentItem: mainView | ||||
58 | 60 | | |||
59 | /** | 61 | /** | ||
60 | * The default width for a column | 62 | * The default width for a column | ||
61 | * default is wide enough for 30 grid units. | 63 | * default is wide enough for 30 grid units. | ||
62 | * Pages can override it with their Layout.fillWidth, | 64 | * Pages can override it with their Layout.fillWidth, | ||
63 | * implicitWidth Layout.minimumWidth etc. | 65 | * implicitWidth Layout.minimumWidth etc. | ||
64 | */ | 66 | */ | ||
65 | property int defaultColumnWidth: Units.gridUnit * 20 | 67 | property int defaultColumnWidth: Units.gridUnit * 20 | ||
66 | 68 | | |||
67 | /** | 69 | /** | ||
68 | * interactive: bool | 70 | * interactive: bool | ||
69 | * If true it will be possible to go back/forward by dragging the | 71 | * If true it will be possible to go back/forward by dragging the | ||
70 | * content themselves with a gesture. | 72 | * content themselves with a gesture. | ||
71 | * Otherwise the only way to go back will be programmatically | 73 | * Otherwise the only way to go back will be programmatically | ||
72 | * default: true | 74 | * default: true | ||
73 | */ | 75 | */ | ||
74 | property alias interactive: mainFlickable.interactive | 76 | // property alias interactive: mainView.interactive | ||
75 | 77 | | |||
76 | //END PROPERTIES | 78 | //END PROPERTIES | ||
77 | 79 | | |||
78 | //BEGIN FUNCTIONS | 80 | //BEGIN FUNCTIONS | ||
79 | /** | 81 | /** | ||
80 | * Pushes a page on the stack. | 82 | * Pushes a page on the stack. | ||
81 | * The page can be defined as a component, item or string. | 83 | * The page can be defined as a component, item or string. | ||
82 | * If an item is used then the page will get re-parented. | 84 | * If an item is used then the page will get re-parented. | ||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Line(s) | 117 | if (pages) { | |||
127 | } | 129 | } | ||
128 | } | 130 | } | ||
129 | 131 | | |||
130 | // initialize the page | 132 | // initialize the page | ||
131 | var container = pagesLogic.initPage(page, properties); | 133 | var container = pagesLogic.initPage(page, properties); | ||
132 | pagesLogic.append(container); | 134 | pagesLogic.append(container); | ||
133 | container.visible = container.page.visible = true; | 135 | container.visible = container.page.visible = true; | ||
134 | 136 | | |||
135 | mainFlickable.currentIndex = container.level; | 137 | mainView.currentIndex = container.level; | ||
136 | return container.page | 138 | return container.page | ||
137 | } | 139 | } | ||
138 | 140 | | |||
139 | /** | 141 | /** | ||
140 | * Pops a page off the stack. | 142 | * Pops a page off the stack. | ||
141 | * @param page If page is specified then the stack is unwound to that page, | 143 | * @param page If page is specified then the stack is unwound to that page, | ||
142 | * to unwind to the first page specify | 144 | * to unwind to the first page specify | ||
143 | * page as null. | 145 | * page as null. | ||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Line(s) | |||||
195 | } | 197 | } | ||
196 | 198 | | |||
197 | function get(idx) { | 199 | function get(idx) { | ||
198 | return pagesLogic.get(idx).page; | 200 | return pagesLogic.get(idx).page; | ||
199 | } | 201 | } | ||
200 | 202 | | |||
201 | //END FUNCTIONS | 203 | //END FUNCTIONS | ||
202 | 204 | | |||
203 | QtObject { | 205 | ListView { | ||
206 | id: mainView | ||||
207 | z: 99 | ||||
208 | anchors.fill: parent | ||||
209 | boundsBehavior: Flickable.StopAtBounds | ||||
210 | orientation: Qt.Horizontal | ||||
211 | snapMode: ListView.SnapToItem | ||||
212 | interactive: root.interactive | ||||
213 | currentIndex: root.currentIndex | ||||
214 | rightMargin: count > 1 ? pagesLogic.get(count-1).page.width - pagesLogic.get(count-1).width : 0 | ||||
215 | preferredHighlightBegin: 0 | ||||
216 | preferredHighlightEnd: 0 | ||||
217 | highlightMoveDuration: Units.longDuration | ||||
218 | onMovementEnded: currentIndex = indexAt(contentX, 0) | ||||
219 | onFlickEnded: onMovementEnded(); | ||||
220 | model: ObjectModel { | ||||
204 | id: pagesLogic | 221 | id: pagesLogic | ||
205 | | ||||
206 | readonly property int count: mainLayout.children.length | | |||
207 | property var componentCache | 222 | property var componentCache | ||
208 | 223 | | |||
209 | property int roundedDefaultColumnWidth: root.width < root.defaultColumnWidth*2 ? root.width : root.defaultColumnWidth | 224 | property int roundedDefaultColumnWidth: root.width < root.defaultColumnWidth*2 ? root.width : root.defaultColumnWidth | ||
210 | 225 | | |||
211 | //NOTE:seems to only work if the array is defined in a declarative way, | 226 | //NOTE:seems to only work if the array is defined in a declarative way, | ||
212 | //the Object in an imperative way, espacially on Android | 227 | //the Object in an imperative way, espacially on Android | ||
213 | Component.onCompleted: { | 228 | Component.onCompleted: { | ||
214 | componentCache = {}; | 229 | componentCache = {}; | ||
215 | } | 230 | } | ||
216 | | ||||
217 | //TODO: remove? | | |||
218 | function get(id) { | | |||
219 | return mainLayout.children[id]; | | |||
220 | } | | |||
221 | | ||||
222 | function append(item) { | | |||
223 | //FIXME: seems that for one loop the x of the item would continue to be 0 | | |||
224 | item.x = item.level * roundedDefaultColumnWidth; | | |||
225 | item.parent = mainLayout; | | |||
226 | } | | |||
227 | | ||||
228 | function clear () { | | |||
229 | while (mainLayout.children.length > 0) { | | |||
230 | remove(0); | | |||
231 | } | | |||
232 | } | | |||
233 | | ||||
234 | function remove(id) { | | |||
235 | if (id < 0 || id >= count) { | | |||
236 | print("Tried to remove an invalid page index:" + id); | | |||
237 | return; | | |||
238 | } | | |||
239 | | ||||
240 | var item = mainLayout.children[id]; | | |||
241 | if (item.owner) { | | |||
242 | item.page.parent = item.owner; | | |||
243 | } | | |||
244 | //FIXME: why reparent ing is necessary? | | |||
245 | //is destroy just an async deleteLater() that isn't executed immediately or it actually leaks? | | |||
246 | item.parent = root; | | |||
247 | item.destroy(); | | |||
248 | } | | |||
249 | | ||||
250 | function initPage(page, properties) { | 231 | function initPage(page, properties) { | ||
251 | var container = containerComponent.createObject(mainLayout, { | 232 | var container = containerComponent.createObject(mainView, { | ||
252 | "level": pagesLogic.count, | 233 | "level": pagesLogic.count, | ||
253 | "page": page | 234 | "page": page | ||
254 | }); | 235 | }); | ||
255 | 236 | | |||
256 | var pageComp; | 237 | var pageComp; | ||
257 | if (page.createObject) { | 238 | if (page.createObject) { | ||
258 | // page defined as component | 239 | // page defined as component | ||
259 | pageComp = page; | 240 | pageComp = page; | ||
Show All 30 Lines | |||||
290 | // the page has to be reparented | 271 | // the page has to be reparented | ||
291 | if (page.parent != container) { | 272 | if (page.parent != container) { | ||
292 | page.parent = container; | 273 | page.parent = container; | ||
293 | } | 274 | } | ||
294 | 275 | | |||
295 | return container; | 276 | return container; | ||
296 | } | 277 | } | ||
297 | } | 278 | } | ||
298 | | ||||
299 | NumberAnimation { | | |||
300 | id: scrollAnim | | |||
301 | target: mainFlickable | | |||
302 | property: "contentX" | | |||
303 | duration: Units.longDuration | | |||
304 | easing.type: Easing.InOutQuad | | |||
305 | } | | |||
306 | | ||||
307 | Flickable { | | |||
308 | id: mainFlickable | | |||
309 | anchors.fill: parent | | |||
310 | boundsBehavior: Flickable.StopAtBounds | | |||
311 | contentWidth: mainLayout.childrenRect.width | | |||
312 | contentHeight: height | | |||
313 | readonly property Item currentItem: { | | |||
314 | var idx = Math.min(currentIndex, pagesLogic.count-1) | | |||
315 | return idx>=0 ? pagesLogic.get(idx).page : null | | |||
316 | } | | |||
317 | //clip only when the app has a sidebar | | |||
318 | clip: root.x > 0 | | |||
319 | | ||||
320 | property int currentIndex: 0 | | |||
321 | property int firstVisibleLevel: Math.round (contentX / pagesLogic.roundedDefaultColumnWidth) | | |||
322 | | ||||
323 | flickDeceleration: Units.gridUnit * 50 | | |||
324 | onCurrentItemChanged: { | | |||
325 | var itemX = pagesLogic.roundedDefaultColumnWidth * currentIndex; | | |||
326 | | ||||
327 | if (itemX >= contentX && mainFlickable.currentItem && itemX + mainFlickable.currentItem.width <= contentX + mainFlickable.width) { | | |||
328 | return; | | |||
329 | } | | |||
330 | | ||||
331 | //this catches 0 and NaN (sometimes at startup width can oddly be nan | | |||
332 | if (!mainFlickable.width) { | | |||
333 | return; | | |||
334 | } | | |||
335 | scrollAnim.running = false; | | |||
336 | scrollAnim.from = contentX; | | |||
337 | if (itemX < contentX || !mainFlickable.currentItem) { | | |||
338 | scrollAnim.to = Math.max(0, Math.min(itemX, mainFlickable.contentWidth - mainFlickable.width)); | | |||
339 | } else { | | |||
340 | scrollAnim.to = Math.max(0, Math.min(itemX - mainFlickable.width + mainFlickable.currentItem.width, mainFlickable.contentWidth - mainFlickable.width)); | | |||
341 | } | | |||
342 | scrollAnim.running = true; | | |||
343 | } | | |||
344 | onMovementEnded: { | | |||
345 | if (mainLayout.childrenRect.width == 0) { | | |||
346 | return; | | |||
347 | } | | |||
348 | | ||||
349 | scrollAnim.running = false; | | |||
350 | scrollAnim.from = contentX; | | |||
351 | scrollAnim.to = pagesLogic.roundedDefaultColumnWidth * firstVisibleLevel | | |||
352 | scrollAnim.running = true; | | |||
353 | | ||||
354 | var mappedCurrentItemPos = currentItem.mapToItem(mainFlickable, 0, 0); | | |||
355 | | ||||
356 | //is the current item out of view? | | |||
357 | if (mappedCurrentItemPos.x < 0) { | | |||
358 | currentIndex = firstVisibleLevel; | | |||
359 | } else if (mappedCurrentItemPos.x + currentItem.width > mainFlickable.width) { | | |||
360 | currentIndex = Math.min(root.depth-1, firstVisibleLevel + Math.floor(mainFlickable.width/pagesLogic.roundedDefaultColumnWidth)-1); | | |||
361 | } | | |||
362 | } | | |||
363 | onFlickEnded: movementEnded(); | | |||
364 | | ||||
365 | Row { | | |||
366 | id: mainLayout | | |||
367 | add: Transition { | | |||
368 | NumberAnimation { | | |||
369 | property: "y" | | |||
370 | from: mainFlickable.height | | |||
371 | to: 0 | | |||
372 | duration: Units.shortDuration | | |||
373 | easing.type: Easing.InOutQuad | | |||
374 | } | | |||
375 | } | | |||
376 | onChildrenChanged: { | | |||
377 | mainFlickable.currentIndex = Math.min(mainFlickable.currentIndex, children.length-1); | | |||
378 | } | | |||
379 | onWidthChanged: { | | |||
380 | //current item in view | | |||
381 | if (children[mainFlickable.currentIndex].x >= mainFlickable.contentX && | | |||
382 | children[mainFlickable.currentIndex].x + children[mainFlickable.currentIndex].width <= mainFlickable.contentX + mainFlickable.width) { | | |||
383 | mainFlickable.contentX = pagesLogic.roundedDefaultColumnWidth * mainFlickable.firstVisibleLevel; | | |||
384 | } else { | | |||
385 | mainFlickable.contentX = Math.max(0, Math.min(width - mainFlickable.width, mainFlickable.currentIndex * pagesLogic.roundedDefaultColumnWidth)); | | |||
386 | } | | |||
387 | | ||||
388 | } | | |||
389 | //onChildrenChanged: mainFlickable.contentX = pagesLogic.roundedDefaultColumnWidth * mainFlickable.firstVisibleLevel | | |||
390 | } | | |||
391 | | ||||
392 | T.ScrollIndicator.horizontal: T.ScrollIndicator { | 279 | T.ScrollIndicator.horizontal: T.ScrollIndicator { | ||
393 | anchors { | 280 | anchors { | ||
394 | left: parent.left | 281 | left: parent.left | ||
395 | right: parent.right | 282 | right: parent.right | ||
396 | bottom: parent.bottom | 283 | bottom: parent.bottom | ||
397 | } | 284 | } | ||
398 | height: Units.smallSpacing | 285 | height: Units.smallSpacing | ||
399 | contentItem: Rectangle { | 286 | contentItem: Rectangle { | ||
Show All 20 Lines | |||||
420 | } | 307 | } | ||
421 | } | 308 | } | ||
422 | 309 | | |||
423 | Component { | 310 | Component { | ||
424 | id: containerComponent | 311 | id: containerComponent | ||
425 | 312 | | |||
426 | MouseArea { | 313 | MouseArea { | ||
427 | id: container | 314 | id: container | ||
428 | height: mainFlickable.height | 315 | height: mainView.height | ||
429 | width: root.width | 316 | width: root.width | ||
430 | state: root.width < root.defaultColumnWidth*2 ? "vertical" : (container.level >= pagesLogic.count - 1 ? "last" : "middle"); | 317 | state: page ? (root.width < root.defaultColumnWidth*2 || pagesLogic.count < 2 ? "vertical" : (container.level >= pagesLogic.count - 1 ? "last" : "middle")) : ""; | ||
431 | 318 | | |||
432 | property int level | 319 | property int level | ||
433 | 320 | | |||
434 | property int hint: page && page.implicitWidth ? page.implicitWidth : root.defaultColumnWidth | 321 | property int hint: page && page.implicitWidth ? page.implicitWidth : root.defaultColumnWidth | ||
435 | property int roundedHint: Math.floor(root.width/hint) > 0 ? root.width/Math.floor(root.width/hint) : root.width | 322 | property int roundedHint: Math.floor(root.width/hint) > 0 ? root.width/Math.floor(root.width/hint) : root.width | ||
436 | 323 | | |||
437 | property Item page | 324 | property Item page | ||
438 | property Item owner | 325 | property Item owner | ||
Show All 23 Lines | |||||
462 | } | 349 | } | ||
463 | states: [ | 350 | states: [ | ||
464 | State { | 351 | State { | ||
465 | name: "vertical" | 352 | name: "vertical" | ||
466 | PropertyChanges { | 353 | PropertyChanges { | ||
467 | target: container | 354 | target: container | ||
468 | width: root.width | 355 | width: root.width | ||
469 | } | 356 | } | ||
357 | PropertyChanges { | ||||
358 | target: container.page.anchors | ||||
359 | rightMargin: 0 | ||||
360 | } | ||||
470 | }, | 361 | }, | ||
471 | State { | 362 | State { | ||
472 | name: "last" | 363 | name: "last" | ||
473 | PropertyChanges { | 364 | PropertyChanges { | ||
474 | target: container | 365 | target: container | ||
475 | width: { | 366 | width: pagesLogic.roundedDefaultColumnWidth | ||
476 | var page = pagesLogic.get(container.level-1); | 367 | } | ||
477 | Math.max(roundedHint, root.width - (page == undefined ? 0 : page.width)) | 368 | PropertyChanges { | ||
369 | target: container.page.anchors | ||||
370 | rightMargin: { | ||||
371 | return -(root.width - pagesLogic.roundedDefaultColumnWidth*2); | ||||
478 | } | 372 | } | ||
479 | } | 373 | } | ||
480 | }, | 374 | }, | ||
481 | State { | 375 | State { | ||
482 | name: "middle" | 376 | name: "middle" | ||
483 | PropertyChanges { | 377 | PropertyChanges { | ||
484 | target: container | 378 | target: container | ||
485 | width: pagesLogic.roundedDefaultColumnWidth | 379 | width: pagesLogic.roundedDefaultColumnWidth | ||
486 | } | 380 | } | ||
381 | PropertyChanges { | ||||
382 | target: container.page.anchors | ||||
383 | rightMargin: 0 | ||||
384 | } | ||||
487 | } | 385 | } | ||
488 | ] | 386 | ] | ||
489 | } | 387 | } | ||
490 | } | 388 | } | ||
491 | 389 | | |||
492 | Component.onCompleted: { | 390 | Component.onCompleted: { | ||
493 | if (initialPage) { | 391 | if (initialPage) { | ||
494 | push(initialPage, null) | 392 | push(initialPage, null) | ||
495 | } | 393 | } | ||
496 | } | 394 | } | ||
497 | } | 395 | } |