| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 The Qt Company Ltd. |
| 4 | ** Contact: http://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at http://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or later as published by the Free |
| 28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
| 29 | ** the packaging of this file. Please review the following information to |
| 30 | ** ensure the GNU General Public License version 2.0 requirements will be |
| 31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| 32 | ** |
| 33 | ** $QT_END_LICENSE$ |
| 34 | ** |
| 35 | ****************************************************************************/ |
| 36 | |
| 37 | #include "qquickdrawer_p.h" |
| 38 | #include "qquickdrawer_p_p.h" |
| 39 | #include "qquickpopupitem_p_p.h" |
| 40 | #include "qquickpopuppositioner_p_p.h" |
| 41 | |
| 42 | #include <QtGui/qstylehints.h> |
| 43 | #include <QtGui/private/qguiapplication_p.h> |
| 44 | #include <QtQml/qqmlinfo.h> |
| 45 | #include <QtQuick/private/qquickwindow_p.h> |
| 46 | #include <QtQuick/private/qquickanimation_p.h> |
| 47 | #include <QtQuick/private/qquicktransition_p.h> |
| 48 | |
| 49 | QT_BEGIN_NAMESPACE |
| 50 | |
| 51 | /*! |
| 52 | \qmltype Drawer |
| 53 | \inherits Popup |
| 54 | //! \instantiates QQuickDrawer |
| 55 | \inqmlmodule QtQuick.Controls |
| 56 | \since 5.7 |
| 57 | \ingroup qtquickcontrols2-navigation |
| 58 | \ingroup qtquickcontrols2-popups |
| 59 | \brief Side panel that can be opened and closed using a swipe gesture. |
| 60 | |
| 61 | Drawer provides a swipe-based side panel, similar to those often used in |
| 62 | touch interfaces to provide a central location for navigation. |
| 63 | |
| 64 | \image qtquickcontrols2-drawer.gif |
| 65 | |
| 66 | Drawer can be positioned at any of the four edges of the content item. |
| 67 | The drawer above is positioned against the left edge of the window. The |
| 68 | drawer is then opened by \e "dragging" it out from the left edge of the |
| 69 | window. |
| 70 | |
| 71 | \code \QtMinorVersion |
| 72 | import QtQuick 2.\1 |
| 73 | import QtQuick.Controls 2.\1 |
| 74 | |
| 75 | ApplicationWindow { |
| 76 | id: window |
| 77 | visible: true |
| 78 | |
| 79 | Drawer { |
| 80 | id: drawer |
| 81 | width: 0.66 * window.width |
| 82 | height: window.height |
| 83 | |
| 84 | Label { |
| 85 | text: "Content goes here!" |
| 86 | anchors.centerIn: parent |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | \endcode |
| 91 | |
| 92 | Drawer is a special type of popup that resides at one of the window \l {edge}{edges}. |
| 93 | By default, Drawer re-parents itself to the window \l {ApplicationWindow::}{overlay}, |
| 94 | and therefore operates on window coordinates. It is also possible to manually set the |
| 95 | \l {Popup::}{parent} to something else to make the drawer operate in a specific |
| 96 | coordinate space. |
| 97 | |
| 98 | Drawer can be configured to cover only part of its window edge. The following example |
| 99 | illustrates how Drawer can be positioned to appear below a window header: |
| 100 | |
| 101 | \code \QtMinorVersion |
| 102 | import QtQuick 2.\1 |
| 103 | import QtQuick.Controls 2.\1 |
| 104 | |
| 105 | ApplicationWindow { |
| 106 | id: window |
| 107 | visible: true |
| 108 | |
| 109 | header: ToolBar { } |
| 110 | |
| 111 | Drawer { |
| 112 | y: header.height |
| 113 | width: window.width * 0.6 |
| 114 | height: window.height - header.height |
| 115 | } |
| 116 | } |
| 117 | \endcode |
| 118 | |
| 119 | The \l position property determines how much of the drawer is visible, as |
| 120 | a value between \c 0.0 and \c 1.0. It is not possible to set the x-coordinate |
| 121 | (or horizontal margins) of a drawer at the left or right window edge, or the |
| 122 | y-coordinate (or vertical margins) of a drawer at the top or bottom window edge. |
| 123 | |
| 124 | In the image above, the application's contents are \e "pushed" across the |
| 125 | screen. This is achieved by applying a translation to the contents: |
| 126 | |
| 127 | \code \QtMinorVersion |
| 128 | import QtQuick 2.\1 |
| 129 | import QtQuick.Controls 2.\1 |
| 130 | |
| 131 | ApplicationWindow { |
| 132 | id: window |
| 133 | width: 200 |
| 134 | height: 228 |
| 135 | visible: true |
| 136 | |
| 137 | Drawer { |
| 138 | id: drawer |
| 139 | width: 0.66 * window.width |
| 140 | height: window.height |
| 141 | } |
| 142 | |
| 143 | Label { |
| 144 | id: content |
| 145 | |
| 146 | text: "Aa" |
| 147 | font.pixelSize: 96 |
| 148 | anchors.fill: parent |
| 149 | verticalAlignment: Label.AlignVCenter |
| 150 | horizontalAlignment: Label.AlignHCenter |
| 151 | |
| 152 | transform: Translate { |
| 153 | x: drawer.position * content.width * 0.33 |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | \endcode |
| 158 | |
| 159 | If you would like the application's contents to stay where they are when |
| 160 | the drawer is opened, don't apply a translation. |
| 161 | |
| 162 | Drawer can be configured as a non-closable persistent side panel by |
| 163 | making the Drawer \l {Popup::modal}{non-modal} and \l {interactive} |
| 164 | {non-interactive}. See the \l {Qt Quick Controls 2 - Side Panel}{Side Panel} |
| 165 | example for more details. |
| 166 | |
| 167 | \note On some platforms, certain edges may be reserved for system |
| 168 | gestures and therefore cannot be used with Drawer. For example, the |
| 169 | top and bottom edges may be reserved for system notifications and |
| 170 | control centers on Android and iOS. |
| 171 | |
| 172 | \sa SwipeView, {Customizing Drawer}, {Navigation Controls}, {Popup Controls} |
| 173 | */ |
| 174 | |
| 175 | class QQuickDrawerPositioner : public QQuickPopupPositioner |
| 176 | { |
| 177 | public: |
| 178 | QQuickDrawerPositioner(QQuickDrawer *drawer) : QQuickPopupPositioner(drawer) { } |
| 179 | |
| 180 | void reposition() override; |
| 181 | }; |
| 182 | |
| 183 | qreal QQuickDrawerPrivate::offsetAt(const QPointF &point) const |
| 184 | { |
| 185 | qreal offset = positionAt(point) - position; |
| 186 | |
| 187 | // don't jump when dragged open |
| 188 | if (offset > 0 && position > 0 && !contains(scenePos: point)) |
| 189 | offset = 0; |
| 190 | |
| 191 | return offset; |
| 192 | } |
| 193 | |
| 194 | qreal QQuickDrawerPrivate::positionAt(const QPointF &point) const |
| 195 | { |
| 196 | Q_Q(const QQuickDrawer); |
| 197 | QQuickWindow *window = q->window(); |
| 198 | if (!window) |
| 199 | return 0; |
| 200 | |
| 201 | switch (edge) { |
| 202 | case Qt::TopEdge: |
| 203 | return point.y() / q->height(); |
| 204 | case Qt::LeftEdge: |
| 205 | return point.x() / q->width(); |
| 206 | case Qt::RightEdge: |
| 207 | return (window->width() - point.x()) / q->width(); |
| 208 | case Qt::BottomEdge: |
| 209 | return (window->height() - point.y()) / q->height(); |
| 210 | default: |
| 211 | return 0; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | QQuickPopupPositioner *QQuickDrawerPrivate::getPositioner() |
| 216 | { |
| 217 | Q_Q(QQuickDrawer); |
| 218 | if (!positioner) |
| 219 | positioner = new QQuickDrawerPositioner(q); |
| 220 | return positioner; |
| 221 | } |
| 222 | |
| 223 | void QQuickDrawerPositioner::reposition() |
| 224 | { |
| 225 | if (m_positioning) |
| 226 | return; |
| 227 | |
| 228 | QQuickDrawer *drawer = static_cast<QQuickDrawer*>(popup()); |
| 229 | QQuickWindow *window = drawer->window(); |
| 230 | if (!window) |
| 231 | return; |
| 232 | |
| 233 | const qreal position = drawer->position(); |
| 234 | QQuickItem * = drawer->popupItem(); |
| 235 | switch (drawer->edge()) { |
| 236 | case Qt::LeftEdge: |
| 237 | popupItem->setX((position - 1.0) * popupItem->width()); |
| 238 | break; |
| 239 | case Qt::RightEdge: |
| 240 | popupItem->setX(window->width() - position * popupItem->width()); |
| 241 | break; |
| 242 | case Qt::TopEdge: |
| 243 | popupItem->setY((position - 1.0) * popupItem->height()); |
| 244 | break; |
| 245 | case Qt::BottomEdge: |
| 246 | popupItem->setY(window->height() - position * popupItem->height()); |
| 247 | break; |
| 248 | } |
| 249 | |
| 250 | QQuickPopupPositioner::reposition(); |
| 251 | } |
| 252 | |
| 253 | void QQuickDrawerPrivate::showOverlay() |
| 254 | { |
| 255 | // managed in setPosition() |
| 256 | } |
| 257 | |
| 258 | void QQuickDrawerPrivate::hideOverlay() |
| 259 | { |
| 260 | // managed in setPosition() |
| 261 | } |
| 262 | |
| 263 | void QQuickDrawerPrivate::resizeOverlay() |
| 264 | { |
| 265 | if (!dimmer || !window) |
| 266 | return; |
| 267 | |
| 268 | QRectF geometry(0, 0, window->width(), window->height()); |
| 269 | |
| 270 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) { |
| 271 | geometry.setY(popupItem->y()); |
| 272 | geometry.setHeight(popupItem->height()); |
| 273 | } else { |
| 274 | geometry.setX(popupItem->x()); |
| 275 | geometry.setWidth(popupItem->width()); |
| 276 | } |
| 277 | |
| 278 | dimmer->setPosition(geometry.topLeft()); |
| 279 | dimmer->setSize(geometry.size()); |
| 280 | } |
| 281 | |
| 282 | static bool isWithinDragMargin(const QQuickDrawer *drawer, const QPointF &pos) |
| 283 | { |
| 284 | switch (drawer->edge()) { |
| 285 | case Qt::LeftEdge: |
| 286 | return pos.x() <= drawer->dragMargin(); |
| 287 | case Qt::RightEdge: |
| 288 | return pos.x() >= drawer->window()->width() - drawer->dragMargin(); |
| 289 | case Qt::TopEdge: |
| 290 | return pos.y() <= drawer->dragMargin(); |
| 291 | case Qt::BottomEdge: |
| 292 | return pos.y() >= drawer->window()->height() - drawer->dragMargin(); |
| 293 | default: |
| 294 | Q_UNREACHABLE(); |
| 295 | break; |
| 296 | } |
| 297 | return false; |
| 298 | } |
| 299 | |
| 300 | bool QQuickDrawerPrivate::startDrag(QEvent *event) |
| 301 | { |
| 302 | Q_Q(QQuickDrawer); |
| 303 | if (!window || !interactive || dragMargin < 0.0 || qFuzzyIsNull(d: dragMargin)) |
| 304 | return false; |
| 305 | |
| 306 | switch (event->type()) { |
| 307 | case QEvent::MouseButtonPress: |
| 308 | if (isWithinDragMargin(drawer: q, pos: static_cast<QMouseEvent *>(event)->windowPos())) { |
| 309 | prepareEnterTransition(); |
| 310 | reposition(); |
| 311 | return handleMouseEvent(item: window->contentItem(), event: static_cast<QMouseEvent *>(event)); |
| 312 | } |
| 313 | break; |
| 314 | |
| 315 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 316 | case QEvent::TouchBegin: |
| 317 | case QEvent::TouchUpdate: |
| 318 | for (const QTouchEvent::TouchPoint &point : static_cast<QTouchEvent *>(event)->touchPoints()) { |
| 319 | if (point.state() == Qt::TouchPointPressed && isWithinDragMargin(drawer: q, pos: point.scenePos())) { |
| 320 | prepareEnterTransition(); |
| 321 | reposition(); |
| 322 | return handleTouchEvent(item: window->contentItem(), event: static_cast<QTouchEvent *>(event)); |
| 323 | } |
| 324 | } |
| 325 | break; |
| 326 | #endif |
| 327 | |
| 328 | default: |
| 329 | break; |
| 330 | } |
| 331 | |
| 332 | return false; |
| 333 | } |
| 334 | |
| 335 | static inline bool keepGrab(QQuickItem *item) |
| 336 | { |
| 337 | return item->keepMouseGrab() || item->keepTouchGrab(); |
| 338 | } |
| 339 | |
| 340 | bool QQuickDrawerPrivate::grabMouse(QQuickItem *item, QMouseEvent *event) |
| 341 | { |
| 342 | Q_Q(QQuickDrawer); |
| 343 | handleMouseEvent(item, event); |
| 344 | |
| 345 | if (!window || !interactive || keepGrab(item: popupItem) || keepGrab(item)) |
| 346 | return false; |
| 347 | |
| 348 | const QPointF movePoint = event->windowPos(); |
| 349 | |
| 350 | // Flickable uses a hard-coded threshold of 15 for flicking, and |
| 351 | // QStyleHints::startDragDistance for dragging. Drawer uses a bit |
| 352 | // larger threshold to avoid being too eager to steal touch (QTBUG-50045) |
| 353 | const int threshold = qMax(a: 20, b: QGuiApplication::styleHints()->startDragDistance() + 5); |
| 354 | bool overThreshold = false; |
| 355 | if (position > 0 || dragMargin > 0) { |
| 356 | const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(d: movePoint.x() - pressPoint.x(), axis: Qt::XAxis, event, startDragThreshold: threshold); |
| 357 | const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(d: movePoint.y() - pressPoint.y(), axis: Qt::YAxis, event, startDragThreshold: threshold); |
| 358 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) |
| 359 | overThreshold = xOverThreshold && !yOverThreshold; |
| 360 | else |
| 361 | overThreshold = yOverThreshold && !xOverThreshold; |
| 362 | } |
| 363 | |
| 364 | // Don't be too eager to steal presses outside the drawer (QTBUG-53929) |
| 365 | if (overThreshold && qFuzzyCompare(p1: position, p2: qreal(1.0)) && !contains(scenePos: movePoint)) { |
| 366 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) |
| 367 | overThreshold = qAbs(t: movePoint.x() - q->width()) < dragMargin; |
| 368 | else |
| 369 | overThreshold = qAbs(t: movePoint.y() - q->height()) < dragMargin; |
| 370 | } |
| 371 | |
| 372 | if (overThreshold) { |
| 373 | popupItem->grabMouse(); |
| 374 | popupItem->setKeepMouseGrab(true); |
| 375 | offset = offsetAt(point: movePoint); |
| 376 | } |
| 377 | |
| 378 | return overThreshold; |
| 379 | } |
| 380 | |
| 381 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 382 | bool QQuickDrawerPrivate::grabTouch(QQuickItem *item, QTouchEvent *event) |
| 383 | { |
| 384 | Q_Q(QQuickDrawer); |
| 385 | bool handled = handleTouchEvent(item, event); |
| 386 | |
| 387 | if (!window || !interactive || keepGrab(item: popupItem) || keepGrab(item) || !event->touchPointStates().testFlag(flag: Qt::TouchPointMoved)) |
| 388 | return handled; |
| 389 | |
| 390 | bool overThreshold = false; |
| 391 | for (const QTouchEvent::TouchPoint &point : event->touchPoints()) { |
| 392 | if (!acceptTouch(point) || point.state() != Qt::TouchPointMoved) |
| 393 | continue; |
| 394 | |
| 395 | const QPointF movePoint = point.scenePos(); |
| 396 | |
| 397 | // Flickable uses a hard-coded threshold of 15 for flicking, and |
| 398 | // QStyleHints::startDragDistance for dragging. Drawer uses a bit |
| 399 | // larger threshold to avoid being too eager to steal touch (QTBUG-50045) |
| 400 | const int threshold = qMax(a: 20, b: QGuiApplication::styleHints()->startDragDistance() + 5); |
| 401 | if (position > 0 || dragMargin > 0) { |
| 402 | const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(d: movePoint.x() - pressPoint.x(), axis: Qt::XAxis, tp: &point, startDragThreshold: threshold); |
| 403 | const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(d: movePoint.y() - pressPoint.y(), axis: Qt::YAxis, tp: &point, startDragThreshold: threshold); |
| 404 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) |
| 405 | overThreshold = xOverThreshold && !yOverThreshold; |
| 406 | else |
| 407 | overThreshold = yOverThreshold && !xOverThreshold; |
| 408 | } |
| 409 | |
| 410 | // Don't be too eager to steal presses outside the drawer (QTBUG-53929) |
| 411 | if (overThreshold && qFuzzyCompare(p1: position, p2: qreal(1.0)) && !contains(scenePos: movePoint)) { |
| 412 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) |
| 413 | overThreshold = qAbs(t: movePoint.x() - q->width()) < dragMargin; |
| 414 | else |
| 415 | overThreshold = qAbs(t: movePoint.y() - q->height()) < dragMargin; |
| 416 | } |
| 417 | |
| 418 | if (overThreshold) { |
| 419 | popupItem->grabTouchPoints(ids: QVector<int>() << touchId); |
| 420 | popupItem->setKeepTouchGrab(true); |
| 421 | offset = offsetAt(point: movePoint); |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | return overThreshold; |
| 426 | } |
| 427 | #endif |
| 428 | |
| 429 | static const qreal openCloseVelocityThreshold = 300; |
| 430 | |
| 431 | bool QQuickDrawerPrivate::blockInput(QQuickItem *item, const QPointF &point) const |
| 432 | { |
| 433 | Q_Q(const QQuickDrawer); |
| 434 | |
| 435 | // We want all events, if mouse/touch is already grabbed. |
| 436 | if (popupItem->keepMouseGrab() || popupItem->keepTouchGrab()) |
| 437 | return true; |
| 438 | |
| 439 | // Don't block input to drawer's children/content. |
| 440 | if (popupItem->isAncestorOf(child: item)) |
| 441 | return false; |
| 442 | |
| 443 | // Don't block outside a drawer's background dimming |
| 444 | if (dimmer && !dimmer->contains(point: dimmer->mapFromScene(point))) |
| 445 | return false; |
| 446 | |
| 447 | // Accept all events within drag area. |
| 448 | if (isWithinDragMargin(drawer: q, pos: point)) |
| 449 | return true; |
| 450 | |
| 451 | // Accept all other events if drawer is modal. |
| 452 | return modal; |
| 453 | } |
| 454 | |
| 455 | bool QQuickDrawerPrivate::handlePress(QQuickItem *item, const QPointF &point, ulong timestamp) |
| 456 | { |
| 457 | offset = 0; |
| 458 | velocityCalculator.startMeasuring(point1: point, timestamp); |
| 459 | |
| 460 | if (!QQuickPopupPrivate::handlePress(item, point, timestamp)) |
| 461 | return false; |
| 462 | |
| 463 | return true; |
| 464 | } |
| 465 | |
| 466 | bool QQuickDrawerPrivate::handleMove(QQuickItem *item, const QPointF &point, ulong timestamp) |
| 467 | { |
| 468 | Q_Q(QQuickDrawer); |
| 469 | if (!QQuickPopupPrivate::handleMove(item, point, timestamp)) |
| 470 | return false; |
| 471 | |
| 472 | // limit/reset the offset to the edge of the drawer when pushed from the outside |
| 473 | if (qFuzzyCompare(p1: position, p2: qreal(1.0)) && !contains(scenePos: point)) |
| 474 | offset = 0; |
| 475 | |
| 476 | bool isGrabbed = popupItem->keepMouseGrab() || popupItem->keepTouchGrab(); |
| 477 | if (isGrabbed) |
| 478 | q->setPosition(positionAt(point) - offset); |
| 479 | |
| 480 | return isGrabbed; |
| 481 | } |
| 482 | |
| 483 | bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, ulong timestamp) |
| 484 | { |
| 485 | if (pressPoint.isNull()) |
| 486 | return false; |
| 487 | if (!popupItem->keepMouseGrab() && !popupItem->keepTouchGrab()) { |
| 488 | velocityCalculator.reset(); |
| 489 | return QQuickPopupPrivate::handleRelease(item, point, timestamp); |
| 490 | } |
| 491 | |
| 492 | velocityCalculator.stopMeasuring(m_point2: point, timestamp); |
| 493 | |
| 494 | qreal velocity = 0; |
| 495 | if (edge == Qt::LeftEdge || edge == Qt::RightEdge) |
| 496 | velocity = velocityCalculator.velocity().x(); |
| 497 | else |
| 498 | velocity = velocityCalculator.velocity().y(); |
| 499 | |
| 500 | // the velocity is calculated so that swipes from left to right |
| 501 | // and top to bottom have positive velocity, and swipes from right |
| 502 | // to left and bottom to top have negative velocity. |
| 503 | // |
| 504 | // - top/left edge: positive velocity opens, negative velocity closes |
| 505 | // - bottom/right edge: negative velocity opens, positive velocity closes |
| 506 | // |
| 507 | // => invert the velocity for bottom and right edges, for the threshold comparison below |
| 508 | if (edge == Qt::RightEdge || edge == Qt::BottomEdge) |
| 509 | velocity = -velocity; |
| 510 | |
| 511 | if (position > 0.7 || velocity > openCloseVelocityThreshold) { |
| 512 | transitionManager.transitionEnter(); |
| 513 | } else if (position < 0.3 || velocity < -openCloseVelocityThreshold) { |
| 514 | transitionManager.transitionExit(); |
| 515 | } else { |
| 516 | switch (edge) { |
| 517 | case Qt::LeftEdge: |
| 518 | if (point.x() - pressPoint.x() > 0) |
| 519 | transitionManager.transitionEnter(); |
| 520 | else |
| 521 | transitionManager.transitionExit(); |
| 522 | break; |
| 523 | case Qt::RightEdge: |
| 524 | if (point.x() - pressPoint.x() < 0) |
| 525 | transitionManager.transitionEnter(); |
| 526 | else |
| 527 | transitionManager.transitionExit(); |
| 528 | break; |
| 529 | case Qt::TopEdge: |
| 530 | if (point.y() - pressPoint.y() > 0) |
| 531 | transitionManager.transitionEnter(); |
| 532 | else |
| 533 | transitionManager.transitionExit(); |
| 534 | break; |
| 535 | case Qt::BottomEdge: |
| 536 | if (point.y() - pressPoint.y() < 0) |
| 537 | transitionManager.transitionEnter(); |
| 538 | else |
| 539 | transitionManager.transitionExit(); |
| 540 | break; |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | bool wasGrabbed = popupItem->keepMouseGrab() || popupItem->keepTouchGrab(); |
| 545 | popupItem->setKeepMouseGrab(false); |
| 546 | popupItem->setKeepTouchGrab(false); |
| 547 | |
| 548 | pressPoint = QPointF(); |
| 549 | touchId = -1; |
| 550 | |
| 551 | return wasGrabbed; |
| 552 | } |
| 553 | |
| 554 | void QQuickDrawerPrivate::handleUngrab() |
| 555 | { |
| 556 | QQuickPopupPrivate::handleUngrab(); |
| 557 | |
| 558 | velocityCalculator.reset(); |
| 559 | } |
| 560 | |
| 561 | static QList<QQuickStateAction> prepareTransition(QQuickDrawer *drawer, QQuickTransition *transition, qreal to) |
| 562 | { |
| 563 | QList<QQuickStateAction> actions; |
| 564 | if (!transition || !QQuickPopupPrivate::get(popup: drawer)->window || !transition->enabled()) |
| 565 | return actions; |
| 566 | |
| 567 | qmlExecuteDeferred(transition); |
| 568 | |
| 569 | QQmlProperty defaultTarget(drawer, QLatin1String("position" )); |
| 570 | QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations(); |
| 571 | int count = animations.count(&animations); |
| 572 | for (int i = 0; i < count; ++i) { |
| 573 | QQuickAbstractAnimation *anim = animations.at(&animations, i); |
| 574 | anim->setDefaultTarget(defaultTarget); |
| 575 | } |
| 576 | |
| 577 | actions << QQuickStateAction(drawer, QLatin1String("position" ), to); |
| 578 | return actions; |
| 579 | } |
| 580 | |
| 581 | bool QQuickDrawerPrivate::prepareEnterTransition() |
| 582 | { |
| 583 | Q_Q(QQuickDrawer); |
| 584 | enterActions = prepareTransition(drawer: q, transition: enter, to: 1.0); |
| 585 | return QQuickPopupPrivate::prepareEnterTransition(); |
| 586 | } |
| 587 | |
| 588 | bool QQuickDrawerPrivate::prepareExitTransition() |
| 589 | { |
| 590 | Q_Q(QQuickDrawer); |
| 591 | exitActions = prepareTransition(drawer: q, transition: exit, to: 0.0); |
| 592 | return QQuickPopupPrivate::prepareExitTransition(); |
| 593 | } |
| 594 | |
| 595 | bool QQuickDrawerPrivate::setEdge(Qt::Edge e) |
| 596 | { |
| 597 | Q_Q(QQuickDrawer); |
| 598 | switch (e) { |
| 599 | case Qt::LeftEdge: |
| 600 | case Qt::RightEdge: |
| 601 | allowVerticalMove = true; |
| 602 | allowVerticalResize = true; |
| 603 | allowHorizontalMove = false; |
| 604 | allowHorizontalResize = false; |
| 605 | break; |
| 606 | case Qt::TopEdge: |
| 607 | case Qt::BottomEdge: |
| 608 | allowVerticalMove = false; |
| 609 | allowVerticalResize = false; |
| 610 | allowHorizontalMove = true; |
| 611 | allowHorizontalResize = true; |
| 612 | break; |
| 613 | default: |
| 614 | qmlWarning(me: q) << "invalid edge value - valid values are: " |
| 615 | << "Qt.TopEdge, Qt.LeftEdge, Qt.RightEdge, Qt.BottomEdge" ; |
| 616 | return false; |
| 617 | } |
| 618 | |
| 619 | edge = e; |
| 620 | return true; |
| 621 | } |
| 622 | |
| 623 | QQuickDrawer::QQuickDrawer(QObject *parent) |
| 624 | : QQuickPopup(*(new QQuickDrawerPrivate), parent) |
| 625 | { |
| 626 | Q_D(QQuickDrawer); |
| 627 | d->dragMargin = QGuiApplication::styleHints()->startDragDistance(); |
| 628 | d->setEdge(Qt::LeftEdge); |
| 629 | |
| 630 | setFocus(true); |
| 631 | setModal(true); |
| 632 | setFiltersChildMouseEvents(true); |
| 633 | setClosePolicy(CloseOnEscape | CloseOnReleaseOutside); |
| 634 | } |
| 635 | |
| 636 | /*! |
| 637 | \qmlproperty enumeration QtQuick.Controls::Drawer::edge |
| 638 | |
| 639 | This property holds the edge of the window at which the drawer will |
| 640 | open from. The acceptable values are: |
| 641 | |
| 642 | \value Qt.TopEdge The top edge of the window. |
| 643 | \value Qt.LeftEdge The left edge of the window (default). |
| 644 | \value Qt.RightEdge The right edge of the window. |
| 645 | \value Qt.BottomEdge The bottom edge of the window. |
| 646 | */ |
| 647 | Qt::Edge QQuickDrawer::edge() const |
| 648 | { |
| 649 | Q_D(const QQuickDrawer); |
| 650 | return d->edge; |
| 651 | } |
| 652 | |
| 653 | void QQuickDrawer::setEdge(Qt::Edge edge) |
| 654 | { |
| 655 | Q_D(QQuickDrawer); |
| 656 | if (d->edge == edge) |
| 657 | return; |
| 658 | |
| 659 | if (!d->setEdge(edge)) |
| 660 | return; |
| 661 | |
| 662 | if (isComponentComplete()) |
| 663 | d->reposition(); |
| 664 | emit edgeChanged(); |
| 665 | } |
| 666 | |
| 667 | /*! |
| 668 | \qmlproperty real QtQuick.Controls::Drawer::position |
| 669 | |
| 670 | This property holds the position of the drawer relative to its final |
| 671 | destination. That is, the position will be \c 0.0 when the drawer |
| 672 | is fully closed, and \c 1.0 when fully open. |
| 673 | */ |
| 674 | qreal QQuickDrawer::position() const |
| 675 | { |
| 676 | Q_D(const QQuickDrawer); |
| 677 | return d->position; |
| 678 | } |
| 679 | |
| 680 | void QQuickDrawer::setPosition(qreal position) |
| 681 | { |
| 682 | Q_D(QQuickDrawer); |
| 683 | position = qBound<qreal>(min: 0.0, val: position, max: 1.0); |
| 684 | if (qFuzzyCompare(p1: d->position, p2: position)) |
| 685 | return; |
| 686 | |
| 687 | d->position = position; |
| 688 | if (isComponentComplete()) |
| 689 | d->reposition(); |
| 690 | if (d->dimmer) |
| 691 | d->dimmer->setOpacity(position); |
| 692 | emit positionChanged(); |
| 693 | } |
| 694 | |
| 695 | /*! |
| 696 | \qmlproperty real QtQuick.Controls::Drawer::dragMargin |
| 697 | |
| 698 | This property holds the distance from the screen edge within which |
| 699 | drag actions will open the drawer. Setting the value to \c 0 or less |
| 700 | prevents opening the drawer by dragging. |
| 701 | |
| 702 | The default value is \c Qt.styleHints.startDragDistance. |
| 703 | |
| 704 | \sa interactive |
| 705 | */ |
| 706 | qreal QQuickDrawer::dragMargin() const |
| 707 | { |
| 708 | Q_D(const QQuickDrawer); |
| 709 | return d->dragMargin; |
| 710 | } |
| 711 | |
| 712 | void QQuickDrawer::setDragMargin(qreal margin) |
| 713 | { |
| 714 | Q_D(QQuickDrawer); |
| 715 | if (qFuzzyCompare(p1: d->dragMargin, p2: margin)) |
| 716 | return; |
| 717 | |
| 718 | d->dragMargin = margin; |
| 719 | emit dragMarginChanged(); |
| 720 | } |
| 721 | |
| 722 | void QQuickDrawer::resetDragMargin() |
| 723 | { |
| 724 | setDragMargin(QGuiApplication::styleHints()->startDragDistance()); |
| 725 | } |
| 726 | |
| 727 | /*! |
| 728 | \since QtQuick.Controls 2.2 (Qt 5.9) |
| 729 | \qmlproperty bool QtQuick.Controls::Drawer::interactive |
| 730 | |
| 731 | This property holds whether the drawer is interactive. A non-interactive |
| 732 | drawer does not react to swipes. |
| 733 | |
| 734 | The default value is \c true. |
| 735 | |
| 736 | \sa dragMargin |
| 737 | */ |
| 738 | bool QQuickDrawer::isInteractive() const |
| 739 | { |
| 740 | Q_D(const QQuickDrawer); |
| 741 | return d->interactive; |
| 742 | } |
| 743 | |
| 744 | void QQuickDrawer::setInteractive(bool interactive) |
| 745 | { |
| 746 | Q_D(QQuickDrawer); |
| 747 | if (d->interactive == interactive) |
| 748 | return; |
| 749 | |
| 750 | setFiltersChildMouseEvents(interactive); |
| 751 | d->interactive = interactive; |
| 752 | emit interactiveChanged(); |
| 753 | } |
| 754 | |
| 755 | bool QQuickDrawer::childMouseEventFilter(QQuickItem *child, QEvent *event) |
| 756 | { |
| 757 | Q_D(QQuickDrawer); |
| 758 | switch (event->type()) { |
| 759 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 760 | case QEvent::TouchUpdate: |
| 761 | return d->grabTouch(item: child, event: static_cast<QTouchEvent *>(event)); |
| 762 | #endif |
| 763 | case QEvent::MouseMove: |
| 764 | return d->grabMouse(item: child, event: static_cast<QMouseEvent *>(event)); |
| 765 | case QEvent::MouseButtonPress: |
| 766 | case QEvent::MouseButtonRelease: |
| 767 | return d->handleMouseEvent(item: child, event: static_cast<QMouseEvent *>(event)); |
| 768 | default: |
| 769 | break; |
| 770 | } |
| 771 | return false; |
| 772 | } |
| 773 | |
| 774 | void QQuickDrawer::mouseMoveEvent(QMouseEvent *event) |
| 775 | { |
| 776 | Q_D(QQuickDrawer); |
| 777 | d->grabMouse(item: d->popupItem, event); |
| 778 | } |
| 779 | |
| 780 | bool QQuickDrawer::overlayEvent(QQuickItem *item, QEvent *event) |
| 781 | { |
| 782 | Q_D(QQuickDrawer); |
| 783 | switch (event->type()) { |
| 784 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 785 | case QEvent::TouchUpdate: |
| 786 | return d->grabTouch(item, event: static_cast<QTouchEvent *>(event)); |
| 787 | #endif |
| 788 | case QEvent::MouseMove: |
| 789 | return d->grabMouse(item, event: static_cast<QMouseEvent *>(event)); |
| 790 | default: |
| 791 | break; |
| 792 | } |
| 793 | return QQuickPopup::overlayEvent(item, event); |
| 794 | } |
| 795 | |
| 796 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 797 | void QQuickDrawer::touchEvent(QTouchEvent *event) |
| 798 | { |
| 799 | Q_D(QQuickDrawer); |
| 800 | d->grabTouch(item: d->popupItem, event); |
| 801 | } |
| 802 | #endif |
| 803 | |
| 804 | void QQuickDrawer::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| 805 | { |
| 806 | Q_D(QQuickDrawer); |
| 807 | QQuickPopup::geometryChanged(newGeometry, oldGeometry); |
| 808 | d->resizeOverlay(); |
| 809 | } |
| 810 | |
| 811 | QT_END_NAMESPACE |
| 812 | |