Kirigami2

templates/OverlayDrawer.qml
1 /*
2  * SPDX-FileCopyrightText: 2012 Marco Martin <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 import QtQuick 2.15
8 import QtQuick.Templates 2.15 as T2
9 import QtQuick.Controls 2.15 as QQC2
10 import org.kde.kirigami 2.11 as Kirigami
11 import "private" as P
12 
13 /**
14  * Overlay Drawers are used to expose additional UI elements needed for
15  * small secondary tasks for which the main UI elements are not needed.
16  * For example in Okular Mobile, an Overlay Drawer is used to display
17  * thumbnails of all pages within a document along with a search field.
18  * This is used for the distinct task of navigating to another page.
19  *
20  * @inherit QtQuick.Controls.Drawer
21  */
22 T2.Drawer {
23  id: root
24 
25  z: modal ? 0 : applicationWindow().overlay.z - 1
26 
27 //BEGIN properties
28  /**
29  * @brief This property tells whether the drawer is open and visible.
30  *
31  * default: ``false``
32  */
33  property bool drawerOpen: false
34 
35  /**
36  * @brief This property tells whether the drawer is in a state between open
37  * and closed.
38  *
39  * The drawer is visible but not completely open. This is usually the case when
40  * the user is dragging the drawer from a screen edge, so the user is "peeking"
41  * at what's in the drawer.
42  *
43  * default: ``false``
44  */
45  property bool peeking: false
46 
47  /**
48  * @brief This property tells whether the drawer is currently opening or closing itself.
49  */
50  readonly property bool animating : enterAnimation.animating || exitAnimation.animating || positionResetAnim.running
51 
52  /**
53  * @brief This property holds whether the drawer can be collapsed to a
54  * very thin, usually icon only sidebar.
55  *
56  * Only modal drawers are collapsible. Collapsible is not supported in
57  * the mobile mode.
58  *
59  * @since 2.5
60  */
61  property bool collapsible: false
62 
63  /**
64  * @brief This property tells whether the drawer is collapsed to a
65  * very thin sidebar, usually icon only.
66  *
67  * When true, the drawer will be collapsed to a very thin sidebar,
68  * usually icon only.
69  *
70  * default: ``false``
71  *
72  * @see collapsible Only collapsible drawers can be collapsed.
73  */
74  property bool collapsed: false
75 
76  /**
77  * @brief This property holds the size of the collapsed drawer.
78  *
79  * For vertical drawers this will be the width of the drawer and for horizontal
80  * drawers this will be the height of the drawer.
81  *
82  * default: ``Units.iconSizes.medium``, just enough to accommodate medium sized icons
83  */
84  property int collapsedSize: Kirigami.Units.iconSizes.medium
85 
86  /**
87  * @brief This property holds the options for handle's open icon.
88  *
89  * This is a grouped property with following components:
90  *
91  * * ``source: var``: The name of a freedesktop-compatible icon.
92  * * ``color: color``: An optional tint color for the icon.
93  *
94  * If no custom icon is set, a menu icon is shown for the application globalDrawer
95  * and an overflow menu icon is shown for the contextDrawer.
96  * That's the default for the GlobalDrawer and ContextDrawer components respectively.
97  *
98  * For OverlayDrawer the default is view-right-close or view-left-close depending on
99  * the drawer location
100  *
101  * @since 2.5
102  */
103  readonly property P.IconPropertiesGroup handleOpenIcon: P.IconPropertiesGroup {
104  source: root.edge === Qt.RightEdge ? "view-right-close" : "view-left-close"
105  }
106 
107  /**
108  * @brief This property holds the options for the handle's close icon.
109  *
110  * This is a grouped property with the following components:
111  * * ``source: var``: The name of a freedesktop-compatible icon.
112  * * ``color: color``: An optional tint color for the icon.
113  *
114  * If no custom icon is set, an X icon is shown,
115  * which will morph into the Menu or overflow icons.
116  *
117  * For OverlayDrawer the default is view-right-new or view-left-new depending on
118  * the drawer location.
119  *
120  * @since 2.5
121  */
122  property P.IconPropertiesGroup handleClosedIcon: P.IconPropertiesGroup {
123  source: root.edge === Qt.RightEdge ? "view-right-new" : "view-left-new"
124  }
125 
126  /**
127  * @brief This property holds the tooltip displayed when the drawer is open.
128  * @since 2.15
129  */
130  property string handleOpenToolTip: qsTr("Close drawer")
131 
132  /**
133  * @brief This property holds the tooltip displayed when the drawer is closed.
134  * @since 2.15
135  */
136  property string handleClosedToolTip: qsTr("Open drawer")
137 
138  /**
139  * @brief This property holds whether the handle is visible, to make opening the
140  * drawer easier.
141  *
142  * Currently supported only on left and right drawers.
143  */
144  property bool handleVisible: typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true
145 
146  /**
147  * @brief Readonly property that points to the item that will act as a physical
148  * handle for the Drawer.
149  * @property MouseArea handle
150  **/
151  readonly property Item handle: MouseArea {
152  id: drawerHandle
153 
154  /*
155  * This property is used to set when the tooltip is visible.
156  * It exists because the text is changed while the tooltip is still visible.
157  */
158  property bool displayToolTip: true
159 
160  z: root.modal ? applicationWindow().overlay.z + (root.position > 0 ? +1 : -1) : root.background.parent.z + 1
161  preventStealing: true
162  hoverEnabled: handleAnchor && handleAnchor.visible
163  parent: applicationWindow().overlay.parent
164 
165  QQC2.ToolButton {
166  anchors.centerIn: parent
167  width: parent.height - Kirigami.Units.smallSpacing * 1.5
168  height: parent.height - Kirigami.Units.smallSpacing * 1.5
169  onClicked: {
170  drawerHandle.displayToolTip = false
171  Qt.callLater(() => root.drawerOpen = !root.drawerOpen)
172  }
173  Accessible.name: root.drawerOpen ? root.handleOpenToolTip : root.handleClosedToolTip
174  visible: !Kirigami.SettingstabletMode && !Kirigami.SettingshasTransientTouchInput
175  }
176  T2.ToolTip.visible: drawerHandle.displayToolTip && containsMouse
177  T2.ToolTip.text: root.drawerOpen ? handleOpenToolTip : handleClosedToolTip
178  T2.ToolTip.delay: Kirigami.Units.toolTipDelay
179 
180  property Item handleAnchor: (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar)
181  ? ((root.edge === Qt.LeftEdge && Qt.application.layoutDirection === Qt.LeftToRight)
182  || (root.edge === Qt.RightEdge && Qt.application.layoutDirection === Qt.RightToLeft)
183  ? applicationWindow().pageStack.globalToolBar.leftHandleAnchor
184  : applicationWindow().pageStack.globalToolBar.rightHandleAnchor)
185  : (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1 ? applicationWindow().header : null)
186 
187  property int startX
188  property int mappedStartX
189 
190  enabled: root.handleVisible
191 
192  onPressed: mouse => {
193  root.peeking = true;
194  startX = mouse.x;
195  mappedStartX = mapToItem(parent, startX, 0).x
196  }
197  onPositionChanged: mouse => {
198  if (!pressed) {
199  return;
200  }
201  const pos = mapToItem(parent, mouse.x - startX, mouse.y);
202  switch(root.edge) {
203  case Qt.LeftEdge:
204  root.position = pos.x/root.contentItem.width;
205  break;
206  case Qt.RightEdge:
207  root.position = (root.parent.width - pos.x - width)/root.contentItem.width;
208  break;
209  default:
210  }
211  }
212  onReleased: mouse => {
213  root.peeking = false;
214  if (Math.abs(mapToItem(parent, mouse.x, 0).x - mappedStartX) < Qt.styleHints.startDragDistance) {
215  if (!root.drawerOpen) {
216  root.close();
217  }
218  root.drawerOpen = !root.drawerOpen;
219  }
220  }
221  onCanceled: {
222  root.peeking = false
223  }
224  x: {
225  switch(root.edge) {
226  case Qt.LeftEdge:
227  return root.background.width * root.position + Kirigami.Units.smallSpacing;
228  case Qt.RightEdge:
229  return drawerHandle.parent.width - (root.background.width * root.position) - width - Kirigami.Units.smallSpacing;
230  default:
231  return 0;
232  }
233  }
234  y: handleAnchor && anchors.bottom ? handleAnchor.Kirigami.ScenePosition.y : 0
235 
236  anchors {
237  bottom: drawerHandle.handleAnchor && drawerHandle.handleAnchor.visible ? undefined : parent.bottom
238  bottomMargin: {
239  if (typeof applicationWindow === "undefined") {
240  return;
241  }
242 
243  let margin = Kirigami.Units.smallSpacing;
244  if (applicationWindow().footer) {
245  margin = applicationWindow().footer.height + Kirigami.Units.smallSpacing;
246  }
247 
248  if(root.parent && root.height < root.parent.height) {
249  margin = root.parent.height - root.height - root.y + Kirigami.Units.smallSpacing;
250  }
251 
252  if (!applicationWindow() || !applicationWindow().pageStack ||
253  !applicationWindow().pageStack.contentItem ||
254  !applicationWindow().pageStack.contentItem.itemAt) {
255  return margin;
256  }
257 
258  let item;
259  if (applicationWindow().pageStack.layers.depth > 1) {
260  item = applicationWindow().pageStack.layers.currentItem;
261  } else {
262  item = applicationWindow().pageStack.contentItem.itemAt(applicationWindow().pageStack.contentItem.contentX + drawerHandle.x, 0);
263  }
264 
265  // try to take the last item
266  if (!item) {
267  item = applicationWindow().pageStack.lastItem;
268  }
269 
270  let pageFooter = item && item.page ? item.page.footer : (item ? item.footer : undefined);
271  if (pageFooter && root.parent) {
272  margin = root.height < root.parent.height ? margin : margin + pageFooter.height
273  }
274 
275  return margin;
276  }
277  Behavior on bottomMargin {
278  NumberAnimation {
279  duration: Kirigami.Units.shortDuration
280  easing.type: Easing.InOutQuad
281  }
282  }
283  }
284 
285  visible: root.enabled && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) && opacity > 0
286  width: handleAnchor && handleAnchor.visible ? handleAnchor.width : Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing*2
287  height: handleAnchor && handleAnchor.visible ? handleAnchor.height : width
288  opacity: root.handleVisible ? 1 : 0
289  Behavior on opacity {
290  NumberAnimation {
291  duration: Kirigami.Units.longDuration
292  easing.type: Easing.InOutQuad
293  }
294  }
295  transform: Translate {
296  id: translateTransform
297  x: root.handleVisible ? 0 : (root.edge === Qt.LeftEdge ? -drawerHandle.width : drawerHandle.width)
298  Behavior on x {
299  NumberAnimation {
300  duration: Kirigami.Units.longDuration
301  easing.type: !root.handleVisible ? Easing.OutQuad : Easing.InQuad
302  }
303  }
304  }
305  }
306 //END properties
307 
308  interactive: modal
309 
310  Kirigami.Theme.inherit: false
311  Kirigami.Theme.colorSet: modal ? Kirigami.Theme.View : Kirigami.Theme.Window
312  Kirigami.Theme.onColorSetChanged: {
313  contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
314  background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
315  }
316 
317 //BEGIN reassign properties
318  //default paddings
319  leftPadding: Kirigami.Units.smallSpacing
320  topPadding: Kirigami.Units.smallSpacing
321  rightPadding: Kirigami.Units.smallSpacing
322  bottomPadding: Kirigami.Units.smallSpacing
323 
324  y: modal ? 0 : ((T2.ApplicationWindow.menuBar ? T2.ApplicationWindow.menuBar.height : 0) + (T2.ApplicationWindow.header ? T2.ApplicationWindow.header.height : 0))
325 
326  height: parent && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) ? (modal ? parent.height : (parent.height - y - (T2.ApplicationWindow.footer ? T2.ApplicationWindow.footer.height : 0))) : implicitHeight
327 
328  parent: modal || edge === Qt.LeftEdge || edge === Qt.RightEdge ? T2.ApplicationWindow.overlay : T2.ApplicationWindow.contentItem
329 
330  edge: Qt.LeftEdge
331  modal: true
332 
333  dragMargin: enabled && (edge === Qt.LeftEdge || edge === Qt.RightEdge) ? Math.min(Kirigami.Units.gridUnit, Qt.styleHints.startDragDistance) : 0
334 
335  contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
336  contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
337 
338  implicitWidth: contentWidth + leftPadding + rightPadding
339  implicitHeight: contentHeight + topPadding + bottomPadding
340 
341  enter: Transition {
342  SequentialAnimation {
343  id: enterAnimation
344  /* NOTE: why this? the running status of the enter transition is not relaible and
345  * the SmoothedAnimation is always marked as non running,
346  * so the only way to get to a reliable animating status is with this
347  */
348  property bool animating
349  ScriptAction {
350  script: {
351  enterAnimation.animating = true
352  // on non modal dialog we don't want drawers in the overlay
353  if (!root.modal) {
354  root.background.parent.parent = applicationWindow().overlay.parent
355  }
356  }
357  }
358  SmoothedAnimation {
359  velocity: 5
360  }
361  ScriptAction {
362  script: enterAnimation.animating = false
363  }
364  }
365  }
366 
367  exit: Transition {
368  SequentialAnimation {
369  id: exitAnimation
370  property bool animating
371  ScriptAction {
372  script: exitAnimation.animating = true
373  }
374  SmoothedAnimation {
375  velocity: 5
376  }
377  ScriptAction {
378  script: exitAnimation.animating = false
379  }
380  }
381  }
382 //END reassign properties
383 
384 
385 //BEGIN signal handlers
386  onCollapsedChanged: {
387  if (Kirigami.SettingsisMobile) {
388  collapsed = false;
389  }
390  if (!__internal.completed) {
391  return;
392  }
393  if ((!collapsible || modal) && collapsed) {
394  collapsed = true;
395  }
396  }
397  onCollapsibleChanged: {
398  if (Kirigami.SettingsisMobile) {
399  collapsible = false;
400  }
401  if (!__internal.completed) {
402  return;
403  }
404  if (!collapsible) {
405  collapsed = false;
406  } else if (modal) {
407  collapsible = false;
408  }
409  }
410  onModalChanged: {
411  if (!__internal.completed) {
412  return;
413  }
414  if (modal) {
415  collapsible = false;
416  }
417  }
418 
419  onPositionChanged: {
420  if (peeking) {
421  visible = true
422  }
423  }
424  onVisibleChanged: {
425  if (peeking) {
426  visible = true
427  } else {
428  drawerOpen = visible;
429  }
430  }
431  onPeekingChanged: {
432  if (peeking) {
433  root.enter.enabled = false;
434  root.exit.enabled = false;
435  } else {
436  drawerOpen = position > 0.5 ? 1 : 0;
437  positionResetAnim.running = true
438  root.enter.enabled = true;
439  root.exit.enabled = true;
440  }
441  }
442  onDrawerOpenChanged: {
443  // sync this property only when the component is properly loaded
444  if (!__internal.completed) {
445  return;
446  }
447  positionResetAnim.running = false;
448  if (drawerOpen) {
449  open();
450  } else {
451  close();
452  }
453  Qt.callLater(() => drawerHandle.displayToolTip = true)
454  }
455 
456  Component.onCompleted: {
457  // if defined as drawerOpen by default in QML, don't animate
458  if (root.drawerOpen) {
459  root.enter.enabled = false;
460  root.visible = true;
461  root.position = 1;
462  root.enter.enabled = true;
463  }
464  __internal.completed = true;
465  contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
466  background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
467  }
468 //END signal handlers
469 
470  // this is as hidden as it can get here
471  property QtObject __internal: QtObject {
472  //here in order to not be accessible from outside
473  property bool completed: false
474  property SequentialAnimation positionResetAnim: SequentialAnimation {
475  id: positionResetAnim
476  property alias to: internalAnim.to
477  NumberAnimation {
478  id: internalAnim
479  target: root
480  to: drawerOpen ? 1 : 0
481  property: "position"
482  duration: (root.position)*Kirigami.Units.longDuration
483  }
484  ScriptAction {
485  script: {
486  root.drawerOpen = internalAnim.to !== 0;
487  }
488  }
489  }
490  readonly property Item statesItem: Item {
491  states: [
492  State {
493  when: root.collapsed
494  PropertyChanges {
495  target: root
496  implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(collapsedSize + leftPadding + rightPadding, Math.round(applicationWindow().width*0.8))
497 
498  implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(collapsedSize + topPadding + bottomPadding, Math.round(applicationWindow().height*0.8))
499  }
500  },
501  State {
502  when: !root.collapsed
503  PropertyChanges {
504  target: root
505  implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8))
506 
507  implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(contentHeight + topPadding + bottomPadding, Math.round(applicationWindow().height*0.4))
508 
509  contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
510  contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
511  }
512  }
513  ]
514  transitions: Transition {
515  reversible: true
516  NumberAnimation {
517  properties: root.edge === Qt.TopEdge || root.edge === Qt.BottomEdge ? "implicitHeight" : "implicitWidth"
518  duration: Kirigami.Units.longDuration
519  easing.type: Easing.InOutQuad
520  }
521  }
522  }
523  }
524 }
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QVector< const char * > &params=QVector< const char * >())
KGuiItem properties()
const QList< QKeySequence > & close()
const QList< QKeySequence > & open()
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.