• Skip to content
  • Skip to link menu
Brand

API Documentation

  1. KDE API Reference
  2. Kirigami
  • KDE Home
  • Contact Us

Quick Links

Skip menu "Kirigami"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • File List
  • Related Pages

Class Picker

About

QtQuick plugins to build user interfaces based on the KDE UX guidelines

Maintainer
Marco Martin
Supported platforms
Android, Linux
Community
IRC: #plasma on Freenode
Mailing list: plasma-devel
Use with CMake
find_package(KF5Kirigami)
target_link_libraries(yourapp KF5::Kirigami)
Clone
git clone git://anongit.kde.org/kirigami1.git
Browse source
Kirigami on cgit.kde.org

Kirigami

  • src
  • controls
  • templates
  • private
PageRow.qml
1 /*
2  * Copyright 2016 Marco Martin <mart@kde.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Library General Public License as
6  * published by the Free Software Foundation; either version 2, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Library General Public License for more details
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 import QtQuick 2.5
21 import QtQuick.Layouts 1.2
22 import org.kde.kirigami 1.0
23 
24 Item {
25  id: root
26 
27 //BEGIN PROPERTIES
31  readonly property alias depth: pagesLogic.count
32 
36  readonly property Item lastItem: pagesLogic.count ? pagesLogic.get(pagesLogic.count - 1).page : null
37 
41  readonly property Item currentItem: mainFlickable.currentItem
42 
46  property alias currentIndex: mainFlickable.currentIndex
47 
51  property variant initialPage
52 
56  property alias contentItem: mainFlickable
57 
64  property int defaultColumnWidth: Units.gridUnit * 20
65 
73  property alias interactive: mainFlickable.interactive
74 
75 //END PROPERTIES
76 
77 //BEGIN FUNCTIONS
99  function push(page, properties) {
100  pop(currentItem);
101 
102  // figure out if more than one page is being pushed
103  var pages;
104  if (page instanceof Array) {
105  pages = page;
106  page = pages.pop();
107  if (page.createObject === undefined && page.parent === undefined && typeof page != "string") {
108  properties = properties || page.properties;
109  page = page.page;
110  }
111  }
112 
113  // push any extra defined pages onto the stack
114  if (pages) {
115  var i;
116  for (i = 0; i < pages.length; i++) {
117  var tPage = pages[i];
118  var tProps;
119  if (tPage.createObject === undefined && tPage.parent === undefined && typeof tPage != "string") {
120  tProps = tPage.properties;
121  tPage = tPage.page;
122  }
123 
124  var container = pagesLogic.initPage(tPage, tProps);
125  pagesLogic.append(container);
126  }
127  }
128 
129  // initialize the page
130  var container = pagesLogic.initPage(page, properties);
131  pagesLogic.append(container);
132  container.visible = container.page.visible = true;
133 
134  mainFlickable.currentIndex = container.level;
135  return container.page
136  }
137 
145  function pop(page) {
146  if (depth == 0) {
147  return;
148  }
149 
150  var oldPage = pagesLogic.get(pagesLogic.count-1).page;
151  if (page !== undefined) {
152  // an unwind target has been specified - pop until we find it
153  while (page != oldPage && pagesLogic.count > 1) {
154  pagesLogic.remove(oldPage.parent.level);
155 
156  oldPage = pagesLogic.get(pagesLogic.count-1).page;
157  }
158  } else {
159  pagesLogic.remove(pagesLogic.count-1);
160  }
161  }
162 
178  function replace(page, properties) {
179  if (currentIndex>=1)
180  pop(pagesLogic.get(currentIndex-1).page);
181  else if (currentIndex==0)
182  pop();
183  else
184  console.warn("There's no page to replace");
185  return push(page, properties);
186  }
187 
192  function clear() {
193  return pagesLogic.clear();
194  }
195 
196  function get(idx) {
197  return pagesLogic.get(idx).page;
198  }
199 
200 //END FUNCTIONS
201 
202  QtObject {
203  id: pagesLogic
204 
205  readonly property int count: mainLayout.children.length
206  property var componentCache
207 
208  property int roundedDefaultColumnWidth: root.width < root.defaultColumnWidth*2 ? root.width : root.defaultColumnWidth
209 
210  //NOTE:seems to only work if the array is defined in a declarative way,
211  //the Object in an imperative way, espacially on Android
212  Component.onCompleted: {
213  componentCache = {};
214  }
215 
216  //TODO: remove?
217  function get(id) {
218  return mainLayout.children[id];
219  }
220 
221  function append(item) {
222  //FIXME: seems that for one loop the x of the item would continue to be 0
223  item.x = item.level * roundedDefaultColumnWidth;
224  item.parent = mainLayout;
225  }
226 
227  function clear () {
228  while (mainLayout.children.length > 0) {
229  remove(0);
230  }
231  }
232 
233  function remove(id) {
234  if (id < 0 || id >= count) {
235  print("Tried to remove an invalid page index:" + id);
236  return;
237  }
238 
239  var item = mainLayout.children[id];
240  if (item.owner) {
241  item.page.parent = item.owner;
242  }
243  //FIXME: why reparent ing is necessary?
244  //is destroy just an async deleteLater() that isn't executed immediately or it actually leaks?
245  item.parent = root;
246  item.destroy();
247  }
248 
249  function initPage(page, properties) {
250  var container = containerComponent.createObject(mainLayout, {
251  "level": pagesLogic.count,
252  "page": page
253  });
254 
255  var pageComp;
256  if (page.createObject) {
257  // page defined as component
258  pageComp = page;
259  } else if (typeof page == "string") {
260  // page defined as string (a url)
261  pageComp = pagesLogic.componentCache[page];
262  if (!pageComp) {
263  pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page);
264  }
265  }
266  if (pageComp) {
267  if (pageComp.status == Component.Error) {
268  throw new Error("Error while loading page: " + pageComp.errorString());
269  } else {
270  // instantiate page from component
271  page = pageComp.createObject(container.pageParent, properties || {});
272  }
273  } else {
274  // copy properties to the page
275  for (var prop in properties) {
276  if (properties.hasOwnProperty(prop)) {
277  page[prop] = properties[prop];
278  }
279  }
280  }
281 
282  container.page = page;
283  if (page.parent == null || page.parent == container.pageParent) {
284  container.owner = null;
285  } else {
286  container.owner = page.parent;
287  }
288 
289  // the page has to be reparented
290  if (page.parent != container) {
291  page.parent = container;
292  }
293 
294  return container;
295  }
296  }
297 
298  NumberAnimation {
299  id: scrollAnim
300  target: mainFlickable
301  property: "contentX"
302  duration: Units.longDuration
303  easing.type: Easing.InOutQuad
304  }
305 
306  Flickable {
307  id: mainFlickable
308  anchors.fill: parent
309  boundsBehavior: Flickable.StopAtBounds
310  contentWidth: mainLayout.childrenRect.width
311  contentHeight: height
312  readonly property Item currentItem: {
313  var idx = Math.min(currentIndex, pagesLogic.count-1)
314  return idx>=0 ? pagesLogic.get(idx).page : null
315  }
316  //clip only when the app has a sidebar
317  clip: root.x > 0
318 
319  property int currentIndex: 0
320  property int firstVisibleLevel: Math.round (contentX / pagesLogic.roundedDefaultColumnWidth)
321 
322  flickDeceleration: Units.gridUnit * 50
323  onCurrentItemChanged: {
324  var itemX = pagesLogic.roundedDefaultColumnWidth * currentIndex;
325 
326  if (itemX >= contentX && mainFlickable.currentItem && itemX + mainFlickable.currentItem.width <= contentX + mainFlickable.width) {
327  return;
328  }
329 
330  //this catches 0 and NaN (sometimes at startup width can oddly be nan
331  if (!mainFlickable.width) {
332  return;
333  }
334  scrollAnim.running = false;
335  scrollAnim.from = contentX;
336  if (itemX < contentX || !mainFlickable.currentItem) {
337  scrollAnim.to = Math.max(0, Math.min(itemX, mainFlickable.contentWidth - mainFlickable.width));
338  } else {
339  scrollAnim.to = Math.max(0, Math.min(itemX - mainFlickable.width + mainFlickable.currentItem.width, mainFlickable.contentWidth - mainFlickable.width));
340  }
341  scrollAnim.running = true;
342  }
343  onMovementEnded: {
344  if (mainLayout.childrenRect.width == 0) {
345  return;
346  }
347 
348  scrollAnim.running = false;
349  scrollAnim.from = contentX;
350  scrollAnim.to = pagesLogic.roundedDefaultColumnWidth * firstVisibleLevel
351  scrollAnim.running = true;
352 
353  var mappedCurrentItemPos = currentItem.mapToItem(mainFlickable, 0, 0);
354 
355  //is the current item out of view?
356  if (mappedCurrentItemPos.x < 0) {
357  currentIndex = firstVisibleLevel;
358  } else if (mappedCurrentItemPos.x + currentItem.width > mainFlickable.width) {
359  currentIndex = Math.min(root.depth-1, firstVisibleLevel + Math.floor(mainFlickable.width/pagesLogic.roundedDefaultColumnWidth)-1);
360  }
361  }
362  onFlickEnded: movementEnded();
363 
364  Row {
365  id: mainLayout
366  add: Transition {
367  NumberAnimation {
368  property: "y"
369  from: mainFlickable.height
370  to: 0
371  duration: Units.shortDuration
372  easing.type: Easing.InOutQuad
373  }
374  }
375  onWidthChanged: {
376  //current item in view
377  if (children[mainFlickable.currentIndex].x >= mainFlickable.contentX &&
378  children[mainFlickable.currentIndex].x + children[mainFlickable.currentIndex].width <= mainFlickable.contentX + mainFlickable.width) {
379  mainFlickable.contentX = pagesLogic.roundedDefaultColumnWidth * mainFlickable.firstVisibleLevel;
380  } else {
381  mainFlickable.contentX = Math.max(0, Math.min(width - mainFlickable.width, mainFlickable.currentIndex * pagesLogic.roundedDefaultColumnWidth));
382  }
383 
384  }
385  //onChildrenChanged: mainFlickable.contentX = pagesLogic.roundedDefaultColumnWidth * mainFlickable.firstVisibleLevel
386  }
387  }
388 
389  Rectangle {
390  height: Units.smallSpacing
391  width: root.width * root.width/mainLayout.width
392  anchors.bottom: parent.bottom
393  color: Theme.textColor
394  opacity: 0
395  x: root.width * mainFlickable.visibleArea.xPosition
396  onXChanged: {
397  opacity = 0.3
398  scrollIndicatorTimer.restart();
399  }
400  Behavior on opacity {
401  OpacityAnimator {
402  duration: Units.longDuration
403  easing.type: Easing.InOutQuad
404  }
405  }
406  Timer {
407  id: scrollIndicatorTimer
408  interval: Units.longDuration * 4
409  onTriggered: parent.opacity = 0;
410  }
411  }
412 
413  Component {
414  id: containerComponent
415 
416  MouseArea {
417  id: container
418  height: mainFlickable.height
419  width: root.width
420  state: pendingState
421  property string pendingState: root.width < root.defaultColumnWidth*2 ? "vertical" : (container.level >= pagesLogic.count - 1 ? "last" : "middle");
422 
423  //HACK
424  onPendingStateChanged: {
425  stateTimer.restart();
426  }
427  Timer {
428  id: stateTimer
429  interval: 150
430  onTriggered: container.state = container.pendingState
431  }
432 
433  property int level
434 
435  property int hint: page && page.implicitWidth ? page.implicitWidth : root.defaultColumnWidth
436  property int roundedHint: Math.floor(root.width/hint) > 0 ? root.width/Math.floor(root.width/hint) : root.width
437 
438  property Item page
439  property Item owner
440  onPageChanged: {
441  page.parent = container;
442  page.anchors.fill = container;
443  }
444  drag.filterChildren: true
445  onClicked: root.currentIndex = level;
446  onFocusChanged: {
447  if (focus) {
448  root.currentIndex = level;
449  }
450  }
451 
452  Rectangle {
453  z: 999
454  anchors {
455  top: parent.top
456  bottom: parent.bottom
457  right: parent.right
458  }
459  width: Math.ceil(Units.smallSpacing / 5)
460  color: Theme.textColor
461  opacity: 0.3
462  visible: container.level < root.depth-1
463  }
464  states: [
465  State {
466  name: "vertical"
467  PropertyChanges {
468  target: container
469  width: root.width
470  }
471  },
472  State {
473  name: "last"
474  PropertyChanges {
475  target: container
476  width: {
477  var page = pagesLogic.get(container.level-1);
478  Math.max(roundedHint, root.width - (page == undefined ? 0 : page.width))
479  }
480  }
481  },
482  State {
483  name: "middle"
484  PropertyChanges {
485  target: container
486  width: pagesLogic.roundedDefaultColumnWidth
487  }
488  }
489  ]
490  transitions: [
491  Transition {
492  from: "last,middle"
493  to: "middle,last"
494  SequentialAnimation {
495  NumberAnimation {
496  property: "width"
497  duration: Units.longDuration
498  easing.type: Easing.InOutQuad
499  }
500  ScriptAction {
501  script: mainFlickable.currentItemChanged();
502  }
503  }
504  }
505  ]
506  }
507  }
508 
509  Component.onCompleted: {
510  if (initialPage) {
511  push(initialPage, null)
512  }
513  }
514 }
org::kde::kirigami::Units::smallSpacing
int smallSpacing
units.smallSpacing is the amount of spacing that should be used around smaller UI elements...
Definition: controls/Units.qml:56
org::kde::kirigami::Units::longDuration
int longDuration
units.longDuration should be used for longer, screen-covering animations, for opening and closing of ...
Definition: controls/Units.qml:75
org::kde::kirigami::Theme
Definition: controls/Theme.qml:22
org::kde::kirigami::Units::shortDuration
int shortDuration
units.shortDuration should be used for short animations, such as accentuating a UI event...
Definition: controls/Units.qml:80
org::kde::kirigami::Units::gridUnit
int gridUnit
The fundamental unit of space that should be used for sizes, expressed in pixels. ...
Definition: controls/Units.qml:31
org::kde::kirigami::Units
Definition: controls/Units.qml:24
This file is part of the KDE documentation.
Documentation copyright © 1996-2017 The KDE developers.
Generated on Fri Feb 17 2017 11:09:23 by doxygen 1.8.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal