Kirigami2

templates/ApplicationHeader.qml
1 /*
2  * SPDX-FileCopyrightText: 2015 Marco Martin <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 import QtQuick 2.5
8 import QtQuick.Controls 2.0 as QQC2
9 import QtQuick.Layouts 1.2
10 import org.kde.kirigami 2.4 as Kirigami
11 import "private"
12 
13 
14 /**
15  * @brief An item that can be used as a title for the application.
16  *
17  * Scrolling the main page will make it taller or shorter (through the point of going away)
18  * It's a behavior similar to the typical mobile web browser addressbar
19  * the minimum, preferred and maximum heights of the item can be controlled with
20  * * ``minimumHeight``: Default is 0, i.e. hidden
21  * * ``preferredHeight``: Default is Kirigami.Units.gridUnit * 1.6
22  * * ``maximumHeight``: Default is Kirigami.Units.gridUnit * 3
23  *
24  * To achieve a titlebar that stays completely fixed just set the 3 sizes to the same value.
25  */
27  id: header
28 
29 //BEGIN properties
30  /**
31  * @brief This property sets the way the separator between pages should be drawn in the header.
32  *
33  * Allowed values are:
34  * * ``Kirigami.ApplicationHeaderStyle.Breadcrumb``: The pages are hierarchical, separated by an arrow.
35  * * ``Kirigami.ApplicationHeaderStyle.TabBar``: The pages are intended to behave like pages of a tabbed view.
36  * and the separator will look limke a dot.
37  *
38  * When the header is in wide screen mode, no separator will be drawn.
39  *
40  * default: ``ApplicationHeaderStyle.Auto``
41  */
42  property int headerStyle: Kirigami.ApplicationHeaderStyle.Auto
43 
44  /**
45  * @brief This property sets whether the back button is enabled.
46  *
47  * default: `when true, there will be a back button present that will make the pagerow scroll back when clicked`
48  */
49  property bool backButtonEnabled: (!titleList.isTabBar && (!Kirigami.Settings.isMobile || Qt.platform.os === "ios"))
50 
51  property Component pageDelegate: Component {
52  Row {
53  height: parent.height
54 
55  spacing: Kirigami.Units.smallSpacing
56 
57  x: Kirigami.Units.smallSpacing
58 
59  Kirigami.Icon {
60  // in tabbar mode this is just a spacer
61  visible: !titleList.wideMode && ((typeof modelData !== "undefined" && modelData > 0) || titleList.internalHeaderStyle === Kirigami.ApplicationHeaderStyle.TabBar)
62  anchors.verticalCenter: parent.verticalCenter
63  height: Kirigami.Units.iconSizes.small
64  width: height
65  selected: header.background && header.background.color && header.background.color === Kirigami.Theme.highlightColor
66  source: titleList.isTabBar ? "" : (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
67  }
68 
69  Kirigami.Heading {
70  id: title
71  width: Math.min(parent.width, Math.min(titleList.width, implicitWidth)) + Kirigami.Units.smallSpacing
72  anchors.verticalCenter: parent.verticalCenter
73  opacity: current ? 1 : 0.4
74  // Scaling animate NativeRendering is too slow
75  renderType: Text.QtRendering
76  color: header.background && header.background.color && header.background.color === Kirigami.Theme.highlightColor ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
77  elide: Text.ElideRight
78  text: page ? page.title : ""
79  font.pointSize: -1
80  font.pixelSize: Math.max(1, titleList.height * 0.7)
81  verticalAlignment: Text.AlignVCenter
82  wrapMode: Text.NoWrap
83  Rectangle {
84  anchors {
85  bottom: parent.bottom
86  left: parent.left
87  right: parent.right
88  }
89  height: Kirigami.Units.smallSpacing
90  color: title.color
91  opacity: 0.6
92  visible: titleList.isTabBar && current
93  }
94  }
95  }
96  }
97 //END properties
98 
99 //BEGIN signal handlers
100  onBackButtonEnabledChanged: {
101  if (backButtonEnabled && !titleList.backButton) {
102  let component = Qt.createComponent(Qt.resolvedUrl("private/BackButton.qml"));
103  titleList.backButton = component.createObject(navButtons);
104  component.destroy();
105  component = Qt.createComponent(Qt.resolvedUrl("private/ForwardButton.qml"));
106  titleList.forwardButton = component.createObject(navButtons);
107  component.destroy();
108  } else if (titleList.backButton) {
109  titleList.backButton.destroy();
110  titleList.forwardButton.destroy();
111  }
112  }
113 
114  Component.onCompleted: print("Warning: ApplicationHeader is deprecated, remove and use the automatic internal toolbar instead.")
115 //END signal handlers
116 
117  Rectangle {
118  anchors {
119  verticalCenter: parent.verticalCenter
120  }
121  visible: titleList.x > 0 && !titleList.atXBeginning
122  height: parent.height * 0.7
123  color: Kirigami.Theme.highlightedTextColor
124  width: Math.ceil(Kirigami.Units.smallSpacing / 6)
125  opacity: 0.4
126  }
127 
128  QQC2.StackView {
129  id: stack
130  anchors {
131  fill: parent
132  leftMargin: navButtons.width
133  rightMargin: __appWindow.contextDrawer && __appWindow.contextDrawer.handleVisible && __appWindow.contextDrawer.handle && __appWindow.contextDrawer.handle.y === 0 ? __appWindow.contextDrawer.handle.width : 0
134  }
135  initialItem: titleList
136 
137  popEnter: Transition {
138  YAnimator {
139  from: -height
140  to: 0
141  duration: Kirigami.Units.longDuration
142  easing.type: Easing.OutCubic
143  }
144  }
145  popExit: Transition {
146  YAnimator {
147  from: 0
148  to: height
149  duration: Kirigami.Units.longDuration
150  easing.type: Easing.OutCubic
151  }
152  }
153 
154  pushEnter: Transition {
155  YAnimator {
156  from: height
157  to: 0
158  duration: Kirigami.Units.longDuration
159  easing.type: Easing.OutCubic
160  }
161  }
162 
163  pushExit: Transition {
164  YAnimator {
165  from: 0
166  to: -height
167  duration: Kirigami.Units.longDuration
168  easing.type: Easing.OutCubic
169  }
170  }
171 
172  replaceEnter: Transition {
173  YAnimator {
174  from: height
175  to: 0
176  duration: Kirigami.Units.longDuration
177  easing.type: Easing.OutCubic
178  }
179  }
180 
181  replaceExit: Transition {
182  YAnimator {
183  from: 0
184  to: -height
185  duration: Kirigami.Units.longDuration
186  easing.type: Easing.OutCubic
187  }
188  }
189  }
190  Kirigami.Separator {
191  id: separator
192  height: parent.height * 0.6
193  visible: navButtons.width > 0
194  anchors {
195  verticalCenter: parent.verticalCenter
196  left: navButtons.right
197  }
198  }
199  Kirigami.Separator {
200  height: parent.height * 0.6
201  visible: stack.anchors.rightMargin > 0
202  anchors {
203  verticalCenter: parent.verticalCenter
204  right: parent.right
205  rightMargin: stack.anchors.rightMargin
206  }
207  }
208  Repeater {
209  model: pageRow.layers.depth -1
210  delegate: Loader {
211  // Don't load async to prevent jumpy behaviour on slower devices as it loads in.
212  // If the title delegate really needs to load async, it should be its responsibility to do it itself.
213  asynchronous: false
214  sourceComponent: header.pageDelegate
215  readonly property Kirigami.Page page: pageRow.layers.get(modelData+1)
216  readonly property bool current: true;
217  Component.onCompleted: stack.push(this)
218  Component.onDestruction: stack.pop()
219  }
220  }
221 
222  Row {
223  id: navButtons
224  anchors {
225  left: parent.left
226  top: parent.top
227  bottom: parent.bottom
228  topMargin: Kirigami.Units.smallSpacing
229  bottomMargin: Kirigami.Units.smallSpacing
230  }
231  Item {
232  height: parent.height
233  width: (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0) && __appWindow.globalDrawer && __appWindow.globalDrawer.handleVisible && __appWindow.globalDrawer.handle && __appWindow.globalDrawer.handle.y === 0 ? __appWindow.globalDrawer.handle.width : 0
234  }
235  }
236 
237  Flickable {
238  id: titleList
239  readonly property bool wideMode: pageRow.hasOwnProperty("wideMode") ? pageRow.wideMode : __appWindow.wideScreen
240  property int internalHeaderStyle: header.headerStyle === Kirigami.ApplicationHeaderStyle.Auto ? (titleList.wideMode ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.Breadcrumb) : header.headerStyle
241  // if scrolling the titlebar should scroll also the pages and vice versa
242  property bool scrollingLocked: (header.headerStyle === Kirigami.ApplicationHeaderStyle.Titles || titleList.wideMode)
243  //uses this to have less strings comparisons
244  property bool scrollMutex
245  property bool isTabBar: header.headerStyle === Kirigami.ApplicationHeaderStyle.TabBar
246 
247  property Item backButton
248  property Item forwardButton
249  clip: true
250 
251 
252  boundsBehavior: Flickable.StopAtBounds
253  readonly property alias model: mainRepeater.model
254  contentWidth: contentItem.width
255  contentHeight: height
256 
257  readonly property int currentIndex: pageRow && pageRow.currentIndex !== undefined ? pageRow.currentIndex : 0
258  readonly property int count: mainRepeater.count
259 
260  function gotoIndex(idx) {
261  // don't actually scroll in widescreen mode
262  if (titleList.wideMode || contentItem.children.length < 2) {
263  return;
264  }
265  listScrollAnim.running = false
266  const pos = titleList.contentX;
267  let destPos;
268  titleList.contentX = Math.max(((contentItem.children[idx] || {x: 0}).x + (contentItem.children[idx] || {width: 0}).width) - titleList.width, Math.min(titleList.contentX, (contentItem.children[idx] || {x: 0}).x));
269  destPos = titleList.contentX;
270  listScrollAnim.from = pos;
271  listScrollAnim.to = destPos;
272  listScrollAnim.running = true;
273  }
274 
275  NumberAnimation {
276  id: listScrollAnim
277  target: titleList
278  property: "contentX"
279  duration: Kirigami.Units.longDuration
280  easing.type: Easing.InOutQuad
281  }
282  Timer {
283  id: contentXSyncTimer
284  interval: 0
285  onTriggered: {
286  titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX;
287  }
288  }
289  onCountChanged: contentXSyncTimer.restart();
290  onCurrentIndexChanged: gotoIndex(currentIndex);
291  onModelChanged: gotoIndex(currentIndex);
292  onContentWidthChanged: gotoIndex(currentIndex);
293 
294  onContentXChanged: {
295  if (movingHorizontally && !titleList.scrollMutex && titleList.scrollingLocked && !pageRow.contentItem.moving) {
296  titleList.scrollMutex = true;
297  pageRow.contentItem.contentX = titleList.contentX - titleList.originX + pageRow.contentItem.originX;
298  titleList.scrollMutex = false;
299  }
300  }
301  onHeightChanged: {
302  titleList.returnToBounds()
303  }
304  onMovementEnded: {
305  if (titleList.scrollingLocked) {
306  //this will trigger snap as well
307  pageRow.contentItem.flick(0,0);
308  }
309  }
310  onFlickEnded: movementEnded();
311 
312  NumberAnimation {
313  id: scrollTopAnimation
314  target: pageRow.currentItem && pageRow.currentItem.flickable ? pageRow.currentItem.flickable : null
315  property: "contentY"
316  to: 0
317  duration: Kirigami.Units.longDuration
318  easing.type: Easing.InOutQuad
319  }
320 
321  Row {
322  id: contentItem
323  spacing: 0
324  Repeater {
325  id: mainRepeater
326  model: pageRow.depth
327  delegate: MouseArea {
328  id: delegate
329  readonly property int currentIndex: index
330  readonly property var currentModelData: modelData
331  clip: true
332 
333  width: {
334  // more columns shown?
335  if (titleList.scrollingLocked && delegateLoader.page) {
336  return delegateLoader.page.width - (index === 0 ? navButtons.width : 0) - (index === pageRow.depth-1 ? stack.anchors.rightMargin : 0);
337  } else {
338  return Math.min(titleList.width, delegateLoader.implicitWidth + Kirigami.Units.smallSpacing);
339  }
340  }
341 
342  height: titleList.height
343  onClicked: mouse => {
344  if (pageRow.currentIndex === modelData) {
345  // scroll up if current otherwise make current
346  if (!pageRow.currentItem.flickable) {
347  return;
348  }
349  if (pageRow.currentItem.flickable.contentY > -__appWindow.header.height) {
350  scrollTopAnimation.to = -pageRow.currentItem.flickable.topMargin;
351  scrollTopAnimation.running = true;
352  }
353 
354  } else {
355  pageRow.currentIndex = modelData;
356  }
357  }
358 
359  Loader {
360  id: delegateLoader
361  height: parent.height
362  x: titleList.wideMode || headerStyle === Kirigami.ApplicationHeaderStyle.Titles ? (Math.min(delegate.width - implicitWidth, Math.max(0, titleList.contentX - delegate.x))) : 0
363  width: parent.width - x
364 
365  Connections {
366  target: delegateLoader.page.Component
367  function onDestruction() {
368  delegateLoader.sourceComponent = null;
369  }
370  }
371 
372  sourceComponent: header.pageDelegate
373 
374  readonly property Kirigami.Page page: pageRow.get(modelData)
375  // NOTE: why not use ListViewCurrentIndex? because listview itself resets
376  // currentIndex in some situations (since here we are using an int as a model,
377  // even more often) so the property binding gets broken
378  readonly property bool current: pageRow.currentIndex === index
379  readonly property int index: parent.currentIndex
380  readonly property var modelData: parent.currentModelData
381  }
382  }
383  }
384  }
385  Connections {
386  target: titleList.scrollingLocked ? pageRow.contentItem : null
387  function onContentXChanged() {
388  if (!titleList.dragging && !titleList.movingHorizontally && !titleList.scrollMutex) {
389  titleList.contentX = pageRow.contentItem.contentX - pageRow.contentItem.originX + titleList.originX;
390  }
391  }
392  }
393  }
394 }
QTextStream & right(QTextStream &stream)
QTextStream & left(QTextStream &stream)
QTextStream & left(QTextStream &s)
QTextStream & right(QTextStream &s)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Feb 7 2023 04:14:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.