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