Plasma

PageRow.qml
1 /*
2  SPDX-FileCopyrightText: 2012 Marco Martin <[email protected]>
3  SPDX-FileCopyrightText: 2011 Nokia Corporation and /or its subsidiary(-ies) <[email protected]>
4 
5  This file is part of the Qt Components project.
6 
7  SPDX-License-Identifier: BSD-3-Clause
8 */
9 
10 import QtQuick 2.0
11 import org.kde.plasma.components 2.0
12 import org.kde.plasma.core 2.0 as PlasmaCore
13 
14 import "../components/private/PageStack.js" as Engine
15 
16 /**
17  * @deprecated use org.kde.kirigami PageRow instead
18  */
19 Item {
20  id: actualRoot
21 
22  width: parent ? parent.width : 0
23  height: parent ? parent.height : 0
24 
25 
26  property int depth: Engine.getDepth()
27  property Item currentPage: null
28  property ToolBar toolBar
29  property var initialPage
30  //A column is wide enough for 30 characters
31  property int columnWidth: Math.round(parent.width/(PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).width*30)) > 0 ? parent.width/Math.round(parent.width/(PlasmaCore.Theme.mSize(PlasmaCore.Theme.defaultFont).width*30)) : width
32  property alias clip: scrollArea.clip
33 
34  // Indicates whether there is an ongoing page transition.
35  property bool busy: internal.ongoingTransitionCount > 0
36 
37  // Pushes a page on the stack.
38  // The page can be defined as a component, item or string.
39  // If an item is used then the page will get re-parented.
40  // If a string is used then it is interpreted as a url that is used to load a page component.
41  //
42  // The page can also be given as an array of pages. In this case all those pages will be pushed
43  // onto the stack. The items in the stack can be components, items or strings just like for single
44  // pages. Additionally an object can be used, which specifies a page and an optional properties
45  // property. This can be used to push multiple pages while still giving each of them properties.
46  // When an array is used the transition animation will only be to the last page.
47  //
48  // The properties argument is optional and allows defining a map of properties to set on the page.
49  // If the immediate argument is true then no transition animation is performed.
50  // Returns the page instance.
51  function push(page, properties, immediate)
52  {
53  var item = Engine.push(page, properties, false, immediate)
54  scrollToLevel(depth)
55  return item
56  }
57 
58  // Pops a page off the stack.
59  // If page is specified then the stack is unwound to that page, to unwind to the first page specify
60  // page as null. If the immediate argument is true then no transition animation is performed.
61  // Returns the page instance that was popped off the stack.
62  function pop(page, immediate)
63  {
64  return Engine.pop(page, immediate);
65  }
66 
67  // Replaces a page on the stack.
68  // See push() for details.
69  function replace(page, properties, immediate)
70  {
71  var item = Engine.push(page, properties, true, immediate);
72  scrollToLevel(depth)
73  return item
74  }
75 
76  // Clears the page stack.
77  function clear()
78  {
79  return Engine.clear();
80  }
81 
82  // Iterates through all pages (top to bottom) and invokes the specified function.
83  // If the specified function returns true the search stops and the find function
84  // returns the page that the iteration stopped at. If the search doesn't result
85  // in any page being found then null is returned.
86  function find(func)
87  {
88  return Engine.find(func);
89  }
90 
91  // Scroll the view to have the page of the given level as first item
92  function scrollToLevel(level)
93  {
94  if (level < 0 || level > depth || root.width < width) {
95  return
96  }
97 
98  scrollAnimation.to = Math.max(0, Math.min(Math.max(0, columnWidth * level - columnWidth), mainFlickable.contentWidth))
99  scrollAnimation.running = true
100  }
101 
102  NumberAnimation {
103  id: scrollAnimation
104  target: mainFlickable
105  properties: "contentX"
106  duration: internal.transitionDuration
107  easing.type: Easing.InOutQuad
108  }
109 
110  // Called when the page stack visibility changes.
111  onVisibleChanged: {
112  if (currentPage) {
113  internal.setPageStatus(currentPage, visible ? PageStatus.Active : PageStatus.Inactive);
114  if (visible)
115  currentPage.visible = currentPage.parent.visible = true;
116  }
117  }
118 
119  onInitialPageChanged: {
120  if (!internal.completed) {
121  return
122  }
123 
124  if (initialPage) {
125  if (depth == 0) {
126  push(initialPage, null, true)
127  } else if (depth == 1) {
128  replace(initialPage, null, true)
129  } else {
130  console.log("Cannot update PageStack.initialPage")
131  }
132  }
133  }
134 
135  Component.onCompleted: {
136  internal.completed = true
137  if (initialPage && depth == 0)
138  push(initialPage, null, true)
139  }
140 
141  QtObject {
142  id: internal
143 
144  // The number of ongoing transitions.
145  property int ongoingTransitionCount: 0
146 
147  //FIXME: there should be a way to access to them without storing it in an ugly way
148  property bool completed: false
149 
150  // Duration of transition animation (in ms)
151  property int transitionDuration: PlasmaCore.Units.longDuration
152 
153  // Sets the page status.
154  function setPageStatus(page, status)
155  {
156  if (page != null) {
157  if (page.status !== undefined) {
158  if (status == PageStatus.Active && page.status == PageStatus.Inactive)
159  page.status = PageStatus.Activating;
160  else if (status == PageStatus.Inactive && page.status == PageStatus.Active)
161  page.status = PageStatus.Deactivating;
162 
163  page.status = status;
164  }
165  }
166  }
167  }
168 
169  ScrollArea {
170  id: scrollArea
171  anchors.fill: parent
172  Flickable {
173  id: mainFlickable
174  anchors.fill: parent
175  interactive: root.width > width
176  boundsBehavior: Flickable.StopAtBounds
177  contentWidth: root.width
178  contentHeight: height
179  Row {
180  id: root
181  spacing: -100
182  width: Math.max((depth-1+children[children.length-1].takenColumns) * columnWidth, childrenRect.width - 100)
183 
184  height: parent.height
185  Behavior on width {
186  NumberAnimation {
187  duration: internal.transitionDuration
188  easing.type: Easing.InOutQuad
189  }
190  }
191  }
192  onMovementEnded: {
193  scrollToLevel(Math.round(contentX/columnWidth)+1)
194  }
195  }
196  }
197 
198  Component {
199  id: svgShadowComponent
200  PlasmaCore.SvgItem {
201  property Item container
202  z: 800
203  svg: PlasmaCore.Svg {imagePath: "widgets/scrollwidget"}
204  elementId: "border-left"
205  width: naturalSize.width
206  opacity: container.pageDepth == actualRoot.depth ? 1 : 0.7
207  anchors {
208  left: container.pageParent.right
209  top: container.pageParent.top
210  bottom: container.pageParent.bottom
211  }
212  Behavior on opacity {
213  NumberAnimation {
214  duration: internal.transitionDuration
215  easing.type: Easing.InOutQuad
216  }
217  }
218  }
219  }
220 
221  // Component for page containers.
222  Component {
223  id: containerComponent
224 
225  Item {
226  id: container
227 
228  implicitWidth: actualContainer.width + 100
229  width: implicitWidth
230  height: parent ? parent.height : 0
231 
232  x: 0
233 
234  // The actual parent of page: page will anchor to that
235  property Item pageParent: actualContainer
236 
237  property int pageDepth: 0
238  Component.onCompleted: {
239  pageDepth = Engine.getDepth() + 1
240  container.z = -Engine.getDepth()
241  }
242 
243  // The states correspond to the different possible positions of the container.
244  state: "Hidden"
245 
246  // The page held by this container.
247  property Item page: null
248 
249  // The owner of the page.
250  property Item owner: null
251 
252  // The width of the longer stack dimension
253  property int stackWidth: Math.max(actualRoot.width, actualRoot.height)
254 
255 
256  // Flag that indicates the container should be cleaned up after the transition has ended.
257  property bool cleanupAfterTransition: false
258 
259  // Flag that indicates if page transition animation is running
260  property bool transitionAnimationRunning: false
261 
262  // State to be set after previous state change animation has finished
263  property string pendingState: "none"
264 
265  //how many columns take the page?
266  property alias takenColumns: actualContainer.takenColumns
267 
268  // Ensures that transition finish actions are executed
269  // in case the object is destroyed before reaching the
270  // end state of an ongoing transition
271  Component.onDestruction: {
272  if (transitionAnimationRunning)
273  transitionEnded();
274  }
275 
276  Item {
277  id: actualContainer
278 
279  anchors {
280  top: parent.top
281  bottom: parent.bottom
282  right: parent.right
283  rightMargin: 100
284  }
285 
286  property int takenColumns: Math.max(1, Math.round(container.page ? container.page.implicitWidth/columnWidth : 1));
287 
288  width: (container.pageDepth >= actualRoot.depth ? Math.min(actualRoot.width, takenColumns*columnWidth) : columnWidth)
289  }
290 
291  Image {
292  z: 800
293  source: "image://appbackgrounds/shadow-right"
294  fillMode: Image.TileVertically
295  opacity: container.pageDepth == actualRoot.depth ? 1 : 0.7
296  anchors {
297  left: actualContainer.right
298  top: actualContainer.top
299  bottom: actualContainer.bottom
300  }
301  Behavior on opacity {
302  NumberAnimation {
303  duration: internal.transitionDuration
304  easing.type: Easing.InOutQuad
305  }
306  }
307  onStatusChanged: {
308  if (status == Image.Error) {
309  var shadow = svgShadowComponent.createObject(container)
310  shadow.container = container
311  destroy()
312  }
313  }
314  }
315 
316  // Sets pending state as current if state change is delayed
317  onTransitionAnimationRunningChanged: {
318  if (!transitionAnimationRunning && pendingState != "none") {
319  state = pendingState;
320  pendingState = "none";
321  }
322  }
323 
324  // Handles state change depending on transition animation status
325  function setState(newState)
326  {
327  if (transitionAnimationRunning)
328  pendingState = newState;
329  else
330  state = newState;
331  }
332 
333  // Performs a push enter transition.
334  function pushEnter(immediate, orientationChanges)
335  {
336  if (!immediate) {
337  setState("Right");
338  }
339  setState("");
340  page.visible = true;
341  if (actualRoot.visible && immediate)
342  internal.setPageStatus(page, PageStatus.Active);
343  }
344 
345  // Performs a push exit transition.
346  function pushExit(replace, immediate, orientationChanges)
347  {
348  if (replace) {
349  setState(immediate ? "Hidden" : "Left");
350  }
351 
352  if (actualRoot.visible && immediate)
353  internal.setPageStatus(page, PageStatus.Inactive);
354  if (replace) {
355  if (immediate)
356  cleanup();
357  else
358  cleanupAfterTransition = true;
359  }
360  }
361 
362  // Performs a pop enter transition.
363  function popEnter(immediate, orientationChanges)
364  {
365  setState("");
366  page.visible = true;
367  if (actualRoot.visible && immediate)
368  internal.setPageStatus(page, PageStatus.Active);
369  }
370 
371  // Performs a pop exit transition.
372  function popExit(immediate, orientationChanges)
373  {
374  setState(immediate ? "Hidden" : "Left");
375 
376  if (actualRoot.visible && immediate)
377  internal.setPageStatus(page, PageStatus.Inactive);
378  if (immediate)
379  cleanup();
380  else
381  cleanupAfterTransition = true;
382  }
383 
384  // Called when a transition has started.
385  function transitionStarted()
386  {
387  container.clip = true
388  transitionAnimationRunning = true;
389  internal.ongoingTransitionCount++;
390  if (actualRoot.visible) {
391  internal.setPageStatus(page, (state == "") ? PageStatus.Activating : PageStatus.Deactivating);
392  }
393  }
394 
395  // Called when a transition has ended.
396  function transitionEnded()
397  {
398  container.clip = false
399  if (state != "")
400  state = "Hidden";
401  if (actualRoot.visible)
402  internal.setPageStatus(page, (state == "") ? PageStatus.Active : PageStatus.Inactive);
403 
404  internal.ongoingTransitionCount--;
405  transitionAnimationRunning = false;
406 
407  if (cleanupAfterTransition) {
408  cleanup();
409  }
410  }
411 
412  states: [
413  // Explicit properties for default state.
414  State {
415  name: ""
416  PropertyChanges { target: container; visible: true; opacity: 1 }
417  PropertyChanges { target: container; width: container.implicitWidth}
418  },
419  // Start state for pop entry, end state for push exit.
420  State {
421  name: "Left"
422  PropertyChanges { target: container; opacity: 0 }
423  PropertyChanges { target: container; width: 100}
424  },
425  // Start state for push entry, end state for pop exit.
426  State {
427  name: "Right"
428  PropertyChanges { target: container; opacity: 0 }
429  PropertyChanges { target: container; width: 100}
430  },
431  // Inactive state.
432  State {
433  name: "Hidden"
434  PropertyChanges { target: container; visible: false }
435  PropertyChanges { target: container; width: container.implicitWidth}
436  }
437  ]
438 
439  transitions: [
440  // Push exit transition
441  Transition {
442  from: ""; to: "Left"
443  SequentialAnimation {
444  ScriptAction { script: transitionStarted() }
445  ParallelAnimation {
446  PropertyAnimation { properties: "width"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
447  PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
448  }
449  ScriptAction { script: transitionEnded() }
450  }
451  },
452  // Pop entry transition
453  Transition {
454  from: "Left"; to: ""
455  SequentialAnimation {
456  ScriptAction { script: transitionStarted() }
457  ParallelAnimation {
458  PropertyAnimation { properties: "width"; easing.type: Easing.OutQuad; duration: internal.transitionDuration }
459  PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
460  }
461  ScriptAction { script: transitionEnded() }
462  }
463  },
464  // Pop exit transition
465  Transition {
466  from: ""; to: "Right"
467  SequentialAnimation {
468  ScriptAction { script: transitionStarted() }
469  ParallelAnimation {
470  PropertyAnimation { properties: "width"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
471  PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
472  }
473  // Workaround for transition animation bug causing ghost view with page pop transition animation
474  // TODO: Root cause still unknown
475  PropertyAnimation {}
476  ScriptAction { script: transitionEnded() }
477  }
478  },
479  // Push entry transition
480  Transition {
481  from: "Right"; to: ""
482  SequentialAnimation {
483  ScriptAction { script: transitionStarted() }
484  ParallelAnimation {
485  PropertyAnimation { properties: "width"; easing.type: Easing.OutQuad; duration: internal.transitionDuration }
486  PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
487  }
488  ScriptAction { script: transitionEnded() }
489  }
490  }
491  ]
492 
493  // Cleans up the container and then destroys it.
494  function cleanup()
495  {
496  if (page != null) {
497  if (page.status == PageStatus.Active) {
498  internal.setPageStatus(page, PageStatus.Inactive)
499  }
500  if (owner != container) {
501  // container is not the owner of the page - re-parent back to original owner
502  page.visible = false;
503  page.anchors.fill = undefined
504  page.parent = owner;
505  }
506  }
507  container.parent = null;
508  container.visible = false;
509  destroy();
510  }
511  }
512  }
513 }
A plasma theme based toolbar.
Definition: qml/ToolBar.qml:14
Q_SCRIPTABLE CaptureState status()
This item takes a Flickable and automatically puts scrollbars in adjusting the layout if needed.
Definition: ScrollArea.qml:20
QTextStream & right(QTextStream &s)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:07:38 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.