Kirigami2

templates/OverlaySheet.qml
1 /*
2  * SPDX-FileCopyrightText: 2016-2020 Marco Martin <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 
8 import QtQuick 2.12
9 import QtQuick.Layouts 1.2
10 import QtQuick.Window 2.2
11 import QtQuick.Controls 2.15 as QQC2
12 import org.kde.kirigami 2.14
13 import QtQuick.Templates 2.0 as T2
14 import "private"
15 import "../private"
16 
17 /**
18  * @brief An overlay sheet that covers the current Page content.
19  *
20  * Its contents can be scrolled up or down, scrolling all the way up or
21  * all the way down, dismisses it.
22  * Use this for big, modal dialogs or information display, that can't be
23  * logically done as a new separate Page, even if potentially
24  * are taller than the screen space.
25  *
26  * @since 2.0
27  * @inherit QtQuick.QtObject
28  */
29 QtObject {
30  id: root
31 
32  Theme.colorSet: Theme.View
33  Theme.inherit: false
34 
35  /**
36  * @brief This property holds the visual content item.
37  * @note The content item is automatically resized to fill the
38  * sheet's view area.
39  *
40  * Conversely, the Sheet will be sized based on the size hints
41  * of the contentItem, so if you need a custom size sheet,
42  * redefine contentWidth and contentHeight of your contentItem
43  */
44  default property Item contentItem
45 
46  /**
47  * @brief This property tells whether the sheet is open and displaying its contents.
48  */
49  property bool sheetOpen
50 
51  /**
52  * @brief This property holds the left padding.
53  *
54  * default: ``Kirigami.Units.largeSpacing``
55  */
56  property int leftPadding: Units.largeSpacing
57 
58  /**
59  * @brief This property holds the top padding.
60  *
61  * default: ``Kirigami.Units.largeSpacing``
62  */
63  property int topPadding: Units.largeSpacing
64 
65  /**
66  * @brief This property holds the right padding.
67  *
68  * default: ``Kirigami.Units.largeSpacing``
69  */
70  property int rightPadding: Units.largeSpacing
71 
72  /**
73  * @brief This property holds the bottom padding.
74  *
75  * default: ``Kirigami.Units.largeSpacing``
76  */
77  property int bottomPadding: Units.largeSpacing
78 
79  /**
80  * @brief This property holds the left inset for the background.
81  *
82  * The inset gets applied to both the content and the background.
83  *
84  * default: ``0``
85  *
86  * @since 2.12
87  */
88  property real leftInset: 0
89 
90  /**
91  * @brief This property holds the top inset for the background.
92  *
93  * The inset gets applied to both the content and the background.
94  *
95  * default: ``0``
96  *
97  * @since 2.12
98  */
99  property real topInset: 0
100 
101  /**
102  * @brief This property holds the right inset for the background.
103  *
104  * The inset gets applied to both the content and the background.
105  *
106  * default: ``0``
107  *
108  * @since 2.12
109  */
110  property real rightInset: 0
111 
112  /**
113  * @brief This property holds the bottom inset for the background.
114  *
115  * The inset gets applied to both the content and the background.
116  *
117  * default: ``0``
118  *
119  * @since 2.12
120  */
121  property real bottomInset: 0
122 
123  /**
124  * @brief This property holds an optional item which will be used as the sheet's header,
125  * and will always be displayed.
126  * @since 5.43
127  */
128  property Item header: Heading {
129  level: 1
130  text: root.title
131  elide: Text.ElideRight
132 
133  // use tooltip for long text that is elided
134  QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
135  QQC2.ToolTip.text: root.title
136  HoverHandler {
137  id: titleHoverHandler
138  }
139  }
140 
141  /**
142  * @brief An optional item which will be used as the sheet's footer,
143  * always kept on screen.
144  * @since 5.43
145  */
146  property Item footer
147 
148  /**
149  * @brief This property holds the background item.
150  *
151  * @note If the background item has no explicit size specified,
152  * it automatically follows the control's size. In most cases,
153  * there is no need to specify width or height for a background item.
154  */
155  property Item background
156 
157  /**
158  * @brief This property sets the visibility of the close button in the top-right corner.
159  *
160  * default: `Only shown in desktop mode`
161  *
162  * @since 5.44
163  */
164  property bool showCloseButton: !Settings.isMobile
165 
166  /**
167  * @brief This property holds the sheet's title.
168  * @note If the header property is set, this will have no effect as the heading will be replaced by the header.
169  * @since 5.84
170  */
171  property string title
172 
173  property Item parent
174 
175  /**
176  * @brief This function opens the overlay sheet.
177  */
178  function open() {
179  openAnimation.running = true;
180  root.sheetOpen = true;
181  contentLayout.initialHeight = contentLayout.height
182  mainItem.visible = true;
183  mainItem.forceActiveFocus();
184  }
185 
186  /**
187  * @brief This function closes the overlay sheet.
188  */
189  function close() {
190  if (root.sheetOpen) {
191  root.sheetOpen = false;
192  }
193  }
194 
195  onBackgroundChanged: {
196  background.parent = contentLayout.parent;
197  background.anchors.fill = contentLayout;
198  background.anchors.margins = -1
199  background.z = -1;
200  }
201  onContentItemChanged: {
202  if (contentItem instanceof Flickable) {
203  scrollView.flickableItem = contentItem;
204  contentItem.parent = scrollView;
205  scrollView.contentItem = contentItem;
206  scrollView.viewContent = contentItem.contentItem;
207  } else {
208  contentItem.parent = contentItemParent;
209  flickableContents.parent = scrollView.flickableItem.contentItem;
210  flickableContents.anchors.top = scrollView.flickableItem.contentItem.top;
211  flickableContents.anchors.left = scrollView.flickableItem.contentItem.left;
212  flickableContents.anchors.right = scrollView.flickableItem.contentItem.right;
213  scrollView.viewContent = flickableContents;
214  contentItem.anchors.left = contentItemParent.left;
215  contentItem.anchors.right = contentItemParent.right;
216  }
217  scrollView.flickableItem.interactive = false;
218  scrollView.flickableItem.flickableDirection = Flickable.VerticalFlick;
219  }
220  onSheetOpenChanged: {
221  if (sheetOpen) {
222  open();
223  } else {
224  closeAnimation.restart()
225  Qt.inputMethod.hide();
226  root.parent.forceActiveFocus();
227  }
228  }
229  onHeaderChanged: headerItem.initHeader()
230  onFooterChanged: {
231  footer.parent = footerParent;
232  footer.anchors.fill = footerParent;
233  }
234 
235  Component.onCompleted: {
236  // ScrollablePage must do things related to parenting of OverlaySheets in its conCompleted, so this must execute later
237  // TODO KF6: port the root object to Popup template?
238  Qt.callLater(() => {
239  if (!root.parent && typeof applicationWindow !== "undefined") {
240  root.parent = applicationWindow().overlay
241  }
242  headerItem.initHeader();
243  });
244  }
245 
246  readonly property Item rootItem: FocusScope {
247  id: mainItem
248  Theme.colorSet: root.Theme.colorSet
249  Theme.inherit: root.Theme.inherit
250  z: 101
251  // we want to be over any possible OverlayDrawers, including handles
252  parent: {
253  if (root.parent && root.parent.ColumnView.view && (root.parent.ColumnView.view === root.parent || root.parent.ColumnView.view === root.parent.parent)) {
254  return root.parent.ColumnView.view.parent;
255  } else if (root.parent && root.parent.overlay) {
256  root.parent.overlay;
257  } else {
258  return root.parent;
259  }
260  }
261 
262  anchors.fill: parent
263 
264  visible: false
265  clip: true
266 
267  // differentiate between mouse and touch
268  HoverHandler {
269  id: mouseHover
270  acceptedDevices: PointerDevice.Mouse
271  }
272 
273  Keys.onEscapePressed: {
274  if (root.sheetOpen) {
275  root.close();
276  } else {
277  event.accepted = false;
278  }
279  }
280 
281  readonly property int contentItemPreferredWidth: root.contentItem.Layout.preferredWidth > 0 ? root.contentItem.Layout.preferredWidth : root.contentItem.implicitWidth
282 
283  readonly property int absoluteContentItemMaximumWidth: width <= 0 ? contentItemPreferredWidth : Math.round(width - Units.largeSpacing * 2)
284  readonly property int contentItemMaximumWidth: root.contentItem.Layout.maximumWidth > 0 ? Math.min(root.contentItem.Layout.maximumWidth, absoluteContentItemMaximumWidth) : width > Units.gridUnit * 30 ? width * 0.95 : absoluteContentItemMaximumWidth
285 
286  onHeightChanged: {
287  var focusItem;
288 
289  focusItem = Window.activeFocusItem;
290 
291  if (!focusItem) {
292  return;
293  }
294 
295  // NOTE: there is no function to know if an item is descended from another,
296  // so we have to walk the parent hierarchy by hand
297  var isDescendent = false;
298  var candidate = focusItem.parent;
299  while (candidate) {
300  if (candidate === root) {
301  isDescendent = true;
302  break;
303  }
304  candidate = candidate.parent;
305  }
306  if (!isDescendent) {
307  return;
308  }
309 
310  var cursorY = 0;
311  if (focusItem.cursorPosition !== undefined) {
312  cursorY = focusItem.positionToRectangle(focusItem.cursorPosition).y;
313  }
314 
315 
316  var pos = focusItem.mapToItem(flickableContents, 0, cursorY - Units.gridUnit*3);
317  // focused item already visible? add some margin for the space of the action buttons
318  if (pos.y >= scrollView.flickableItem.contentY && pos.y <= scrollView.flickableItem.contentY + scrollView.flickableItem.height - Units.gridUnit * 8) {
319  return;
320  }
321  scrollView.flickableItem.contentY = pos.y;
322  }
323 
324  ParallelAnimation {
325  id: openAnimation
326  property int margins: Units.gridUnit * 5
327  NumberAnimation {
328  target: outerFlickable
329  properties: "contentY"
330  from: -outerFlickable.height
331  to: outerFlickable.openPosition
332  duration: Units.longDuration
333  easing.type: Easing.OutQuad
334  }
335  OpacityAnimator {
336  target: mainItem
337  from: 0
338  to: 1
339  duration: Units.longDuration
340  easing.type: Easing.InQuad
341  }
342  }
343 
344  NumberAnimation {
345  id: resetAnimation
346  target: outerFlickable
347  properties: "contentY"
348  from: outerFlickable.contentY
349  to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 || scrollView.flickableItem.contentHeight < outerFlickable.height
350  ? outerFlickable.openPosition
351  : outerFlickable.contentHeight - outerFlickable.height + outerFlickable.topEmptyArea + headerItem.height + footerItem.height
352  duration: Units.longDuration
353  easing.type: Easing.OutQuad
354  }
355 
356  SequentialAnimation {
357  id: closeAnimation
358  ParallelAnimation {
359  NumberAnimation {
360  target: outerFlickable
361  properties: "contentY"
362  from: outerFlickable.contentY + (contentLayout.initialHeight - contentLayout.height)
363  to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 ? -mainItem.height : outerFlickable.contentHeight
364  duration: Units.longDuration
365  easing.type: Easing.InQuad
366  }
367  OpacityAnimator {
368  target: mainItem
369  from: 1
370  to: 0
371  duration: Units.longDuration
372  easing.type: Easing.InQuad
373  }
374  }
375  ScriptAction {
376  script: {
377  contentLayout.initialHeight = 0
378  scrollView.flickableItem.contentY = -mainItem.height;
379  mainItem.visible = false;
380  }
381  }
382  }
383  Rectangle {
384  anchors.fill: parent
385  color: "black"
386  opacity: 0.3 * Math.min(
387  (Math.min(outerFlickable.contentY + outerFlickable.height, outerFlickable.height) / outerFlickable.height),
388  (2 + (outerFlickable.contentHeight - outerFlickable.contentY - outerFlickable.topMargin - outerFlickable.bottomMargin)/outerFlickable.height))
389  }
390 
391  MouseArea {
392  anchors.fill: parent
393  drag.filterChildren: true
394  hoverEnabled: true
395 
396  onPressed: {
397  let pos = mapToItem(contentLayout, mouse.x, mouse.y);
398  if (contentLayout.contains(pos) && mouseHover.hovered) { // only on mouse event, not touch
399  // disable dragging the sheet with a mouse
400  outerFlickable.interactive = false
401  }
402  }
403  onReleased: {
404  let pos = mapToItem(contentLayout, mouse.x, mouse.y);
405  if (!contentLayout.contains(pos)) {
406  root.close();
407  }
408  // enable dragging of sheet once mouse is not clicked
409  outerFlickable.interactive = true
410  }
411 
412 
413  Item {
414  id: flickableContents
415 
416  readonly property real listHeaderHeight: scrollView.flickableItem ? -scrollView.flickableItem.originY : 0
417 
418  y: (scrollView.contentItem !== flickableContents ? -scrollView.flickableItem.contentY - listHeaderHeight - (headerItem.visible ? headerItem.height : 0): 0)
419 
420  width: mainItem.contentItemPreferredWidth <= 0 ? mainItem.width : (mainItem.contentItemMaximumWidth > 0 ? Math.min( mainItem.contentItemMaximumWidth, Math.max( mainItem.width/2, mainItem.contentItemPreferredWidth ) ) : Math.max( mainItem.width / 2, mainItem.contentItemPreferredWidth ) ) + leftPadding + rightPadding
421 
422 
423  implicitHeight: scrollView.viewContent === flickableContents ? root.contentItem.height + topPadding + bottomPadding : 0
424 
425  Connections {
426  target: enabled ? flickableContents.Window.activeFocusItem : null
427  enabled: flickableContents.focus && flickableContents.Window.activeFocusItem && flickableContents.Window.activeFocusItem.hasOwnProperty("text")
428  function onTextChanged() {
429  if (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height > mainItem.Window.height) {
430  scrollView.flickableItem.contentY += (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height) - mainItem.Window.height
431  }
432  }
433  }
434 
435  Item {
436  id: contentItemParent
437  anchors {
438  fill: parent
439  leftMargin: leftPadding
440  topMargin: topPadding
441  rightMargin: rightPadding
442  bottomMargin: bottomPadding
443  }
444  }
445  }
446 
447  Connections {
448  target: scrollView.flickableItem
449  property real oldContentHeight: 0
450  function onContentHeightChanged() {
451  if (openAnimation.running) {
452  openAnimation.running = false;
453  open();
454  } else {
455  // repositioning is relevant only when the content height is less than the viewport height.
456  // In that case the sheet looks like a dialog and should be centered. there is also a corner case when now is bigger then the viewport but prior to the
457  // resize event it was smaller, also in this case we need repositioning
458  if (scrollView.animatedContentHeight < outerFlickable.height
459  || scrollView.flickableItem.oldContentHeight < outerFlickable.height
460  ) {
461  outerFlickable.adjustPosition();
462  }
463  oldContentHeight = scrollView.animatedContentHeight
464  }
465  }
466  }
467 
468  Flickable {
469  id: outerFlickable
470  anchors.fill: parent
471  contentWidth: width
472  topMargin: height
473  bottomMargin: height
474  // +1: we need the flickable to be always interactive
475  contentHeight: Math.max(height+1, scrollView.animatedContentHeight + topEmptyArea)
476 
477  // readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Units.gridUnit * 3)
478  readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Units.gridUnit * 3)
479 
480  readonly property real openPosition: Math.max(0, outerFlickable.height - outerFlickable.contentHeight + headerItem.height + footerItem.height) + height/2 - contentLayout.height/2;
481 
482  onOpenPositionChanged: {
483  if (openAnimation.running) {
484  openAnimation.running = false;
485  root.open();
486  } else if (root.sheetOpen) {
487  adjustPosition();
488  }
489  }
490 
491  property int oldContentY: NaN
492  property int oldContentHeight: 0
493  property bool lastMovementWasDown: false
494  property real startDraggingPos
495  property bool layoutMovingGuard: false
496  WheelHandler {
497  target: outerFlickable
498  scrollFlickableTarget: false
499  }
500 
501  function adjustPosition() {
502  if(layoutMovingGuard) return;
503 
504  if (openAnimation.running) {
505  openAnimation.running = false;
506  open()
507  } else {
508  resetAnimation.running = false;
509  contentY = openPosition;
510  }
511  }
512 
513  // disable dragging the sheet with a mouse on header bar
514  MouseArea {
515  anchors.fill: parent
516  onPressed: {
517  if (mouseHover.hovered) { // only on mouse event, not touch
518  outerFlickable.interactive = false
519  }
520  }
521  onReleased: outerFlickable.interactive = true
522  }
523 
524  onContentYChanged: {
525  if (scrollView.userInteracting) {
526  return;
527  }
528 
529  let startPos = -scrollView.flickableItem.topMargin - flickableContents.listHeaderHeight;
530  let pos = contentY - topEmptyArea - flickableContents.listHeaderHeight;
531  let endPos = scrollView.animatedContentHeight - scrollView.flickableItem.height + scrollView.flickableItem.bottomMargin - flickableContents.listHeaderHeight;
532 
533  layoutMovingGuard = true;
534  if (endPos - pos > 0) {
535  contentLayout.y = Math.round(Math.max(root.topInset, scrollView.flickableItem.topMargin - pos - flickableContents.listHeaderHeight));
536  } else if (scrollView.flickableItem.topMargin - pos < 0) {
537  contentLayout.y = Math.round(endPos - pos + root.topInset);
538  }
539  layoutMovingGuard = false;
540 
541  scrollView.flickableItem.contentY = Math.max(
542  startPos, Math.min(pos, endPos));
543 
544  lastMovementWasDown = contentY < oldContentY;
545  oldContentY = contentY;
546  }
547 
548  onFlickEnded: {
549  if (openAnimation.running || closeAnimation.running) {
550  return;
551  }
552  if (scrollView.flickableItem.atYBeginning ||scrollView.flickableItem.atYEnd) {
553  resetAnimation.restart();
554  }
555  }
556 
557  onDraggingChanged: {
558  if (dragging) {
559  startDraggingPos = contentY;
560  return;
561  }
562 
563  let shouldClose = false;
564 
565  // close
566  if (scrollView.flickableItem.atYBeginning) {
567  if (startDraggingPos - contentY > Units.gridUnit * 4 &&
568  contentY < -Units.gridUnit * 4 &&
569  lastMovementWasDown) {
570  shouldClose = true;
571  }
572  }
573 
574  if (scrollView.flickableItem.atYEnd) {
575  if (contentY - startDraggingPos > Units.gridUnit * 4 &&
576  contentY > contentHeight - height + Units.gridUnit * 4 &&
577  !lastMovementWasDown) {
578  shouldClose = true;
579  }
580  }
581 
582  if (shouldClose) {
583  root.sheetOpen = false
584  } else if (scrollView.flickableItem.atYBeginning || scrollView.flickableItem.atYEnd) {
585  resetAnimation.restart();
586  }
587  }
588 
589  onHeightChanged: {
590  adjustPosition();
591  }
592 
593  onContentHeightChanged: {
594  // repositioning is relevant only when the content height is less than the viewport height.
595  // In that case the sheet looks like a dialog and should be centered. there is also a corner case when now is bigger then the viewport but prior to the
596  // resize event it was smaller, also in this case we need repositioning
597  if (contentHeight < height || oldContentHeight < height) {
598  adjustPosition();
599  }
600  oldContentHeight = contentHeight;
601  }
602 
603  ColumnLayout {
604  id: contentLayout
605  spacing: 0
606  // Its events should be filtered but not scrolled
607  parent: outerFlickable
608  anchors.horizontalCenter: parent.horizontalCenter
609  width: mainItem.contentItemPreferredWidth <= 0 ? mainItem.width : (mainItem.contentItemMaximumWidth > 0 ? Math.min( mainItem.contentItemMaximumWidth, Math.max( mainItem.width/2, mainItem.contentItemPreferredWidth ) ) : Math.max( mainItem.width / 2, mainItem.contentItemPreferredWidth ) ) - root.leftInset - root.rightInset + root.leftPadding + root.rightPadding
610  height: Math.min(implicitHeight, parent.height) - root.topInset - root.bottomInset
611  property real initialHeight
612 
613  Behavior on height {
614  NumberAnimation {
615  duration: Units.shortDuration
616  easing.type: Easing.InOutCubic
617  }
618  }
619 
620  // Even though we're not actually using any shadows here,
621  // we're using a ShadowedRectangle instead of a regular
622  // rectangle because it allows fine-grained control over which
623  // corners to round, which we need here
624  ShadowedRectangle {
625  id: headerItem
626  Layout.fillWidth: true
627  Layout.alignment: Qt.AlignTop
628  //Layout.margins: 1
629  visible: root.header || root.showCloseButton
630  implicitHeight: Math.max(headerParent.implicitHeight, closeIcon.height) + Units.smallSpacing * 2
631  z: 2
632  corners.topLeftRadius: Units.smallSpacing
633  corners.topRightRadius: Units.smallSpacing
634  Theme.colorSet: Theme.Header
635  Theme.inherit: false
636  color: Theme.backgroundColor
637 
638  function initHeader() {
639  if (header) {
640  header.parent = headerParent;
641  header.anchors.fill = headerParent;
642 
643  // TODO: special case for actual ListViews
644  }
645  }
646 
647  Item {
648  id: headerParent
649  implicitHeight: header ? header.implicitHeight : 0
650  anchors {
651  fill: parent
652  leftMargin: Units.largeSpacing
653  margins: Units.smallSpacing
654  rightMargin: (root.showCloseButton ? closeIcon.width : 0) + Units.smallSpacing
655  }
656  }
657  Icon {
658  id: closeIcon
659 
660  readonly property bool tallHeader: headerItem.height > (Units.iconSizes.smallMedium + Units.largeSpacing + Units.largeSpacing)
661 
662  anchors {
663  right: parent.right
664  rightMargin: Units.largeSpacing
665  verticalCenter: headerItem.verticalCenter
666  margins: Units.smallSpacing
667  }
668 
669  // Apply the changes to the anchors imperatively, to first disable an anchor point
670  // before setting the new one, so the icon don't grow unexpectedly
671  onTallHeaderChanged: {
672  if (tallHeader) {
673  // We want to position the close button in the top-right corner if the header is very tall
674  anchors.verticalCenter = undefined
675  anchors.topMargin = Units.largeSpacing
676  anchors.top = headerItem.top
677  } else {
678  // but we want to vertically center it in a short header
679  anchors.top = undefined
680  anchors.topMargin = undefined
681  anchors.verticalCenter = headerItem.verticalCenter
682  }
683  }
684  Component.onCompleted: tallHeaderChanged()
685 
686  z: 3
687  visible: root.showCloseButton
688  width: Units.iconSizes.smallMedium
689  height: width
690  source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
691  active: closeMouseArea.containsMouse
692  MouseArea {
693  id: closeMouseArea
694  hoverEnabled: true
695  anchors.fill: parent
696  onClicked: root.close();
697  }
698  }
699  Separator {
700  anchors {
701  right: parent.right
702  left: parent.left
703  top: parent.bottom
704  }
705  }
706  }
707 
708  QQC2.ScrollView {
709  id: scrollView
710 
711  // Don't do the automatic interactive enable/disable
712  // canFlickWithMouse: true
713  property Item viewContent
714  property real animatedContentHeight: flickableItem.contentHeight
715  property bool userInteracting: false
716  Layout.fillWidth: true
717  Layout.fillHeight: true
718  property alias flickableItem: scrollView.contentItem
719 
720  focus: false
721 
722  implicitHeight: flickableItem.contentHeight
723  Layout.maximumHeight: flickableItem.contentHeight
724 
725  Layout.alignment: Qt.AlignTop
726 
727  // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
728  QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
729 
730  Behavior on animatedContentHeight {
731  NumberAnimation {
732  duration: Units.shortDuration
733  easing.type: Easing.InOutCubic
734  }
735  }
736  }
737 
738  Item { Layout.fillHeight: true }
739 
740  Connections {
741  target: scrollView.flickableItem
742  property real oldContentY: 0
743  function onContentYChanged() {
744  if (outerFlickable.moving) {
745  oldContentY = scrollView.flickableItem.contentY;
746  return;
747  }
748  scrollView.userInteracting = true;
749 
750  let diff = scrollView.flickableItem.contentY - oldContentY
751 
752  outerFlickable.contentY = outerFlickable.contentY + diff;
753 
754  if (diff > 0) {
755  contentLayout.y = Math.max(root.topInset, contentLayout.y - diff);
756  } else if (scrollView.flickableItem.contentY < outerFlickable.topEmptyArea + headerItem.height) {
757  contentLayout.y = Math.min(outerFlickable.topEmptyArea, contentLayout.y + (contentLayout.y - diff)) + root.topInset;
758  }
759 
760  oldContentY = scrollView.flickableItem.contentY;
761  scrollView.userInteracting = false;
762  }
763  }
764  Item {
765  visible: footerItem.visible
766  implicitHeight: footerItem.height
767  }
768  }
769 
770  // footer item is outside the layout as it should never scroll away
771 
772  // Even though we're not actually using any shadows here,
773  // we're using a ShadowedRectangle instead of a regular
774  // rectangle because it allows fine-grained control over which
775  // corners to round, which we need here
776  ShadowedRectangle {
777  id: footerItem
778  width: contentLayout.width - 2
779  corners.bottomLeftRadius: Units.smallSpacing
780  corners.bottomRightRadius: Units.smallSpacing
781  parent: outerFlickable
782  x: contentLayout.x + 1
783  y: Math.min(parent.height, contentLayout.y + contentLayout.height -1) - height
784  visible: root.footer
785  implicitHeight: footerParent.implicitHeight + Units.smallSpacing * 2 + extraMargin
786  Theme.colorSet: Theme.Window
787  Theme.inherit: false
788  color: Theme.backgroundColor
789 
790  // Show an extra margin when:
791  // * the application is in mobile mode
792  // * it doesn't use toolbarapplicationheader
793  // * the bottom screen controls are visible
794  // * the sheet is displayed *under* the controls
795  property int extraMargin: (!root.parent ||
796  !Settings.isMobile ||
797  typeof applicationWindow === "undefined" ||
798  (root.parent === applicationWindow().overlay) ||
799  !applicationWindow().controlsVisible ||
800  (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle === ApplicationHeaderStyle.ToolBar) ||
801  (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0))
802  ? 0 : Units.gridUnit * 3
803 
804  z: 2
805  Item {
806  id: footerParent
807  implicitHeight: footer ? footer.implicitHeight : 0
808  anchors {
809  top: parent.top
810  left: parent.left
811  right: parent.right
812  margins: Units.smallSpacing
813  }
814  }
815 
816  Separator {
817  anchors {
818  right: parent.right
819  left: parent.left
820  bottom: parent.top
821  leftMargin: -1
822  rightMargin: -1
823  }
824  }
825  }
826  }
827  }
828  }
829 }
int gridUnit
This class contains global kirigami settings about the current device setup It is exposed to QML as t...
Definition: settings.h:16
QTextStream & right(QTextStream &stream)
int shortDuration
Handles scrolling for a Flickable and 2 attached ScrollBars.
Definition: wheelhandler.h:167
int longDuration
QTextStream & left(QTextStream &stream)
bool isMobile
This property holds whether the application is running on a small mobile device such as a mobile phon...
Definition: settings.h:33
KGuiItem properties()
A visual separator.
Definition: Separator.qml:18
int largeSpacing
void initHeader(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id=0)
QTextStream & left(QTextStream &s)
int smallSpacing
A heading label used for subsections of texts.
Definition: Heading.qml:36
QTextStream & right(QTextStream &s)
QQmlPropertyMap iconSizes
Class for rendering an icon in UI.
Definition: icon.h:26
const QList< QKeySequence > & open()
QObject * parent() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 19 2022 04:08:31 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.