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 as Kirigami
13 import QtQuick.Templates 2.0 as T2
14 import "private" as P
15 import "../private" as PP
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  Kirigami.Theme.colorSet: Kirigami.Theme.View
33  Kirigami.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: Kirigami.Units.largeSpacing
57 
58  /**
59  * @brief This property holds the top padding.
60  *
61  * default: ``Kirigami.Units.largeSpacing``
62  */
63  property int topPadding: Kirigami.Units.largeSpacing
64 
65  /**
66  * @brief This property holds the right padding.
67  *
68  * default: ``Kirigami.Units.largeSpacing``
69  */
70  property int rightPadding: Kirigami.Units.largeSpacing
71 
72  /**
73  * @brief This property holds the bottom padding.
74  *
75  * default: ``Kirigami.Units.largeSpacing``
76  */
77  property int bottomPadding: Kirigami.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: Kirigami.Heading {
129  level: 2
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: !Kirigami.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  Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
249  Kirigami.Theme.inherit: root.Kirigami.Theme.inherit
250  z: 101
251  // we want to be over any possible OverlayDrawers, including handles
252  parent: {
253  if (root.parent && root.parent.Kirigami.ColumnView.view && (root.parent.Kirigami.ColumnView.view === root.parent || root.parent.Kirigami.ColumnView.view === root.parent.parent)) {
254  return root.parent.Kirigami.ColumnView.view.parent;
255  } else if (root.parent && root.parent.overlay) {
256  return 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: event => {
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 - Kirigami.Units.largeSpacing * 2)
284  readonly property int contentItemMaximumWidth: root.contentItem.Layout.maximumWidth > 0 ? Math.min(root.contentItem.Layout.maximumWidth, absoluteContentItemMaximumWidth) : width > Kirigami.Units.gridUnit * 30 ? width * 0.95 : absoluteContentItemMaximumWidth
285 
286  onHeightChanged: {
287  const focusItem = Window.activeFocusItem;
288 
289  if (!focusItem) {
290  return;
291  }
292 
293  // NOTE: there is no function to know if an item is descended from another,
294  // so we have to walk the parent hierarchy by hand
295  let isDescendent = false;
296  let candidate = focusItem.parent;
297  while (candidate) {
298  if (candidate === root) {
299  isDescendent = true;
300  break;
301  }
302  candidate = candidate.parent;
303  }
304  if (!isDescendent) {
305  return;
306  }
307 
308  let cursorY = 0;
309  if (focusItem.cursorPosition !== undefined) {
310  cursorY = focusItem.positionToRectangle(focusItem.cursorPosition).y;
311  }
312 
313 
314  const pos = focusItem.mapToItem(flickableContents, 0, cursorY - Units.gridUnit*3);
315  // focused item already visible? add some margin for the space of the action buttons
316  if (pos.y >= scrollView.flickableItem.contentY && pos.y <= scrollView.flickableItem.contentY + scrollView.flickableItem.height - Kirigami.Units.gridUnit * 8) {
317  return;
318  }
319  scrollView.flickableItem.contentY = pos.y;
320  }
321 
322  ParallelAnimation {
323  id: openAnimation
324  property int margins: Kirigami.Units.gridUnit * 5
325  NumberAnimation {
326  target: outerFlickable
327  properties: "contentY"
328  from: -outerFlickable.height
329  to: outerFlickable.openPosition
330  duration: Kirigami.Units.longDuration
331  easing.type: Easing.OutQuad
332  }
333  OpacityAnimator {
334  target: mainItem
335  from: 0
336  to: 1
337  duration: Kirigami.Units.longDuration
338  easing.type: Easing.InQuad
339  }
340  }
341 
342  NumberAnimation {
343  id: resetAnimation
344  target: outerFlickable
345  properties: "contentY"
346  from: outerFlickable.contentY
347  to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 || scrollView.flickableItem.contentHeight < outerFlickable.height
348  ? outerFlickable.openPosition
349  : outerFlickable.contentHeight - outerFlickable.height + outerFlickable.topEmptyArea + headerItem.height + footerItem.height
350  duration: Kirigami.Units.longDuration
351  easing.type: Easing.OutQuad
352  }
353 
354  SequentialAnimation {
355  id: closeAnimation
356  ParallelAnimation {
357  NumberAnimation {
358  target: outerFlickable
359  properties: "contentY"
360  from: outerFlickable.contentY + (contentLayout.initialHeight - contentLayout.height)
361  to: outerFlickable.visibleArea.yPosition < (1 - outerFlickable.visibleArea.heightRatio)/2 ? -mainItem.height : outerFlickable.contentHeight
362  duration: Kirigami.Units.longDuration
363  easing.type: Easing.InQuad
364  }
365  OpacityAnimator {
366  target: mainItem
367  from: 1
368  to: 0
369  duration: Kirigami.Units.longDuration
370  easing.type: Easing.InQuad
371  }
372  }
373  ScriptAction {
374  script: {
375  contentLayout.initialHeight = 0
376  scrollView.flickableItem.contentY = -mainItem.height;
377  mainItem.visible = false;
378  }
379  }
380  }
381  Rectangle {
382  anchors.fill: parent
383  color: "black"
384  opacity: 0.3 * Math.min(
385  (Math.min(outerFlickable.contentY + outerFlickable.height, outerFlickable.height) / outerFlickable.height),
386  (2 + (outerFlickable.contentHeight - outerFlickable.contentY - outerFlickable.topMargin - outerFlickable.bottomMargin)/outerFlickable.height))
387  }
388 
389  MouseArea {
390  anchors.fill: parent
391  drag.filterChildren: true
392  hoverEnabled: true
393 
394  onPressed: mouse => {
395  const pos = mapToItem(contentLayout, mouse.x, mouse.y);
396  if (contentLayout.contains(pos) && mouseHover.hovered) { // only on mouse event, not touch
397  // disable dragging the sheet with a mouse
398  outerFlickable.interactive = false
399  }
400  }
401  onReleased: mouse => {
402  const pos = mapToItem(contentLayout, mouse.x, mouse.y);
403  if (!contentLayout.contains(pos)) {
404  root.close();
405  }
406  // enable dragging of sheet once mouse is not clicked
407  outerFlickable.interactive = true
408  }
409 
410 
411  Item {
412  id: flickableContents
413 
414  readonly property real listHeaderHeight: scrollView.flickableItem ? -scrollView.flickableItem.originY : 0
415 
416  y: (scrollView.contentItem !== flickableContents ? -scrollView.flickableItem.contentY - listHeaderHeight - (headerItem.visible ? headerItem.height : 0): 0)
417 
418  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
419 
420 
421  implicitHeight: scrollView.viewContent === flickableContents ? root.contentItem.height + topPadding + bottomPadding : 0
422 
423  Connections {
424  target: enabled ? flickableContents.Window.activeFocusItem : null
425  enabled: flickableContents.focus && flickableContents.Window.activeFocusItem && flickableContents.Window.activeFocusItem.hasOwnProperty("text")
426  function onTextChanged() {
427  if (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height > mainItem.Window.height) {
428  scrollView.flickableItem.contentY += (Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height) - mainItem.Window.height
429  }
430  }
431  }
432 
433  Item {
434  id: contentItemParent
435  anchors {
436  fill: parent
437  leftMargin: leftPadding
438  topMargin: topPadding
439  rightMargin: rightPadding
440  bottomMargin: bottomPadding
441  }
442  }
443  }
444 
445  Connections {
446  target: scrollView.flickableItem
447  property real oldContentHeight: 0
448  function onContentHeightChanged() {
449  if (openAnimation.running) {
450  openAnimation.running = false;
451  open();
452  } else {
453  // repositioning is relevant only when the content height is less than the viewport height.
454  // 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
455  // resize event it was smaller, also in this case we need repositioning
456  if (scrollView.animatedContentHeight < outerFlickable.height
457  || scrollView.flickableItem.oldContentHeight < outerFlickable.height
458  ) {
459  outerFlickable.adjustPosition();
460  }
461  oldContentHeight = scrollView.animatedContentHeight
462  }
463  }
464  }
465 
466  Flickable {
467  id: outerFlickable
468  anchors.fill: parent
469  contentWidth: width
470  topMargin: height
471  bottomMargin: height
472  // +1: we need the flickable to be always interactive
473  contentHeight: Math.max(height+1, scrollView.animatedContentHeight + topEmptyArea)
474 
475  // readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Kirigami.Units.gridUnit * 3)
476  readonly property int topEmptyArea: Math.max(height-scrollView.animatedContentHeight, Kirigami.Units.gridUnit * 3)
477 
478  readonly property real openPosition: Math.max(0, outerFlickable.height - outerFlickable.contentHeight + headerItem.height + footerItem.height) + height/2 - contentLayout.height/2;
479 
480  onOpenPositionChanged: {
481  if (openAnimation.running) {
482  openAnimation.running = false;
483  root.open();
484  } else if (root.sheetOpen) {
485  adjustPosition();
486  }
487  }
488 
489  property real oldContentY: NaN
490  property real oldContentHeight: 0
491  property bool lastMovementWasDown: false
492  property real startDraggingPos
493  property bool layoutMovingGuard: false
494  Kirigami.WheelHandler {
495  target: outerFlickable
496  scrollFlickableTarget: false
497  }
498 
499  function adjustPosition() {
500  if(layoutMovingGuard) return;
501 
502  if (openAnimation.running) {
503  openAnimation.running = false;
504  open()
505  } else {
506  resetAnimation.running = false;
507  contentY = openPosition;
508  }
509  }
510 
511  // disable dragging the sheet with a mouse on header bar
512  MouseArea {
513  anchors.fill: parent
514  onPressed: mouse => {
515  if (mouseHover.hovered) { // only on mouse event, not touch
516  outerFlickable.interactive = false
517  }
518  }
519  onReleased: mouse => {
520  outerFlickable.interactive = true
521  }
522  }
523 
524  onContentYChanged: {
525  if (scrollView.userInteracting) {
526  return;
527  }
528 
529  const startPos = -scrollView.flickableItem.topMargin - flickableContents.listHeaderHeight;
530  const pos = contentY - topEmptyArea - flickableContents.listHeaderHeight;
531  const 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 > Kirigami.Units.gridUnit * 4 &&
568  contentY < -Kirigami.Units.gridUnit * 4 &&
569  lastMovementWasDown) {
570  shouldClose = true;
571  }
572  }
573 
574  if (scrollView.flickableItem.atYEnd) {
575  if (contentY - startDraggingPos > Kirigami.Units.gridUnit * 4 &&
576  contentY > contentHeight - height + Kirigami.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: Kirigami.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  Kirigami.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) + Kirigami.Units.smallSpacing * 2
631  z: 2
632  corners.topLeftRadius: Kirigami.Units.smallSpacing
633  corners.topRightRadius: Kirigami.Units.smallSpacing
634  Kirigami.Theme.colorSet: Kirigami.Theme.Header
635  Kirigami.Theme.inherit: false
636  color: Kirigami.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: Kirigami.Units.largeSpacing
653  margins: Kirigami.Units.smallSpacing
654  rightMargin: (root.showCloseButton ? closeIcon.width : 0) + Kirigami.Units.smallSpacing
655  }
656  }
657  Kirigami.Icon {
658  id: closeIcon
659 
660  readonly property bool tallHeader: headerItem.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
661 
662  anchors {
663  right: parent.right
664  rightMargin: Kirigami.Units.largeSpacing
665  verticalCenter: headerItem.verticalCenter
666  margins: Kirigami.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 = Kirigami.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: Kirigami.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: mouse => root.close();
697  }
698  }
699  Kirigami.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: Kirigami.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  const 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 + root.topInset, contentLayout.y - diff);
758  }
759  oldContentY = scrollView.flickableItem.contentY;
760  scrollView.userInteracting = false;
761  }
762  }
763  Item {
764  visible: footerItem.visible
765  implicitHeight: footerItem.height
766  }
767  }
768 
769  // footer item is outside the layout as it should never scroll away
770 
771  // Even though we're not actually using any shadows here,
772  // we're using a ShadowedRectangle instead of a regular
773  // rectangle because it allows fine-grained control over which
774  // corners to round, which we need here
775  Kirigami.ShadowedRectangle {
776  id: footerItem
777  width: contentLayout.width
778  corners.bottomLeftRadius: Kirigami.Units.smallSpacing
779  corners.bottomRightRadius: Kirigami.Units.smallSpacing
780  parent: outerFlickable
781  x: contentLayout.x
782  y: Math.min(parent.height, contentLayout.y + contentLayout.height) - height
783  visible: root.footer
784  implicitHeight: footerParent.implicitHeight + Kirigami.Units.smallSpacing * 2 + extraMargin
785  Kirigami.Theme.colorSet: Kirigami.Theme.Window
786  Kirigami.Theme.inherit: false
787  color: Kirigami.Theme.backgroundColor
788 
789  // Show an extra margin when:
790  // * the application is in mobile mode
791  // * it doesn't use toolbarapplicationheader
792  // * the bottom screen controls are visible
793  // * the sheet is displayed *under* the controls
794  property int extraMargin: (!root.parent ||
795  !Kirigami.Settings.isMobile ||
796  typeof applicationWindow === "undefined" ||
797  (root.parent === applicationWindow().overlay) ||
798  !applicationWindow().controlsVisible ||
799  (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar) ||
800  (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0))
801  ? 0 : Kirigami.Units.gridUnit * 3
802 
803  z: 2
804  Item {
805  id: footerParent
806  implicitHeight: footer ? footer.implicitHeight : 0
807  anchors {
808  top: parent.top
809  left: parent.left
810  right: parent.right
811  margins: Kirigami.Units.smallSpacing
812  }
813  }
814 
815  Kirigami.Separator {
816  anchors {
817  right: parent.right
818  left: parent.left
819  bottom: parent.top
820  }
821  }
822  }
823  }
824  }
825  }
826 }
int gridUnit
QTextStream & right(QTextStream &stream)
QTextStream & left(QTextStream &stream)
KGuiItem properties()
void initHeader(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id=0)
QTextStream & left(QTextStream &s)
QTextStream & right(QTextStream &s)
const QList< QKeySequence > & open()
QObject * parent() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Jan 29 2023 04:11:03 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.