diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2021-10-07 22:27:32 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2021-12-02 12:52:58 +0100 |
commit | e17bfffc075202ff9ee8fba0f378f95037514740 (patch) | |
tree | e5c380c95731fc2aadc8d032a8a9ecb1a3284039 /examples | |
parent | 06e68e0cc06e5c53654bf1d9fa17376317640bc8 (diff) |
Add TapHandler.gesturePolicy: DragWithinBounds enum value; examples
On a touchscreen, right-clicking is not directly possible; so sometimes
a long-press gesture is used as a substitute. The next thing a UI
designer would want would then be a way of showing feedback that a
long-press is in progress, rather than simply waiting for the long-press
to occur and then surprising the user with some instant action.
For example, a menu might begin to open as the user holds down the
touchpoint; but before the long-press gesture is complete, the user can
simply release, to cancel the gesture and close the menu. The timeHeld
property could drive the animation, to avoid needing a separate
animation type; in fact the reason timeHeld exists is to make it easy
to emulate this sort of touch-press animation, like one that occurs on
touchscreens since Windows 7.
But after the menu is open, the user would probably expect to be able to
drag the finger to a menu item and release, to select the menu item. For
such a purpose, the existing gesture policies weren't very useful: each
of them resets the timeHeld property if the user drags beyond the drag
threshold; so if the user expects to drag and release over a menu item,
then the timeHeld property cannot drive the menu-opening animation,
because the menu would disappear as soon as the user drags a little.
So it makes more sense to have a gesturePolicy that acts like
WithinBounds, but also applies the same policy to the timeHeld property
and the longPressed signal. We don't care about the drag threshold:
if the user is holding down a finger, it's considered to be a
long-press-in-progress, regardless of how far it has moved since press
(as long as it stays within the parent's bounds).
An example of such a menu is added. The menu must have TapHandler as
its root object, because it reacts to press-and-drag within some larger
item, larger than the menu itself. For example such a menu could be
used in a canvas-like application (drawing, diagramming, dragging things
like photos or file icons, or something like that): dragging items on
the canvas is possible, but long-pressing anywhere will open a context
menu. But in this example so far, only the menu is implemented.
It's a pie menu, because those are particularly touch-friendly; but
perhaps for the mouse, a conventional context menu would be used.
[ChangeLog][QtQuick][Event Handlers] TapHandler now has one more
gesturePolicy value: DragWithinBounds; it is similar to WithinBounds,
except that timeHeld is not reset during dragging, and the longPressed
signal can be emitted regardless of the drag threshold. This is useful
for implementing press-drag-release components such as menus, while
using timeHeld to directly drive an "opening" animation.
Change-Id: I298f8b1ad8f8d7d3c241ef4fdd68e7ec8d8b5bdd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/quick/pointerhandlers/components/QuadPieMenu.qml | 158 | ||||
-rw-r--r-- | examples/quick/pointerhandlers/pieMenu.qml | 77 | ||||
-rw-r--r-- | examples/quick/pointerhandlers/pointerhandlers.qml | 1 | ||||
-rw-r--r-- | examples/quick/pointerhandlers/tapHandler.qml | 30 |
4 files changed, 259 insertions, 7 deletions
diff --git a/examples/quick/pointerhandlers/components/QuadPieMenu.qml b/examples/quick/pointerhandlers/components/QuadPieMenu.qml new file mode 100644 index 0000000000..151d5fa04f --- /dev/null +++ b/examples/quick/pointerhandlers/components/QuadPieMenu.qml @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Shapes + +TapHandler { + property var labels: [ "upperLeft", "upperRight", "lowerRight", "lowerLeft" ] + signal triggered(string text) + + id: menuTap + gesturePolicy: TapHandler.DragWithinBounds + onPressedChanged: if (pressed) { + impl.x = point.position.x - impl.width / 2 + impl.y = point.position.y - impl.width / 2 + } else { + if (impl.highlightedShape) + menuTap.triggered(impl.highlightedShape.text) + } + + property Item impl: Item { + parent: menuTap.parent + width: 100 + height: 100 + scale: Math.min(1, Math.max(0, menuTap.timeHeld * 4)) + opacity: scale * 2 + visible: menuTap.pressed + property Shape highlightedShape: null + + component PieSegment : Shape { + id: shape + property int orientation: Qt.TopRightCorner + property alias text: text.text + + width: 100 + height: 100 + containsMode: Shape.FillContains + + property bool highlighted: menuTap.pressed && + shape.contains(shape.mapFromItem(menuTap.parent, menuTap.point.position)) + onHighlightedChanged: { + if (highlighted) + impl.highlightedShape = shape + else if (impl.highlightedShape === shape) + impl.highlightedShape = null + } + + ShapePath { + fillColor: highlighted ? "darkturquoise" : "aliceblue" + PathSvg { + id: svgPath + path: switch (orientation) { + case Qt.TopRightCorner: + return "M75,50 l 25,0 a50,50 0 0,0 -50,-50 l 0,25 a25,25 0 0,1 25,25"; + case Qt.BottomRightCorner: + return "M75,50 l 25,0 a50,50 0 0,1 -50,50 l 0,-25 a25,25 0 0,0 25,-25"; + case Qt.TopLeftCorner: + return "M50,25 l 0,-25 a50,50 0 0,0 -50,50 l 25,0 a25,25 0 0,1 25,-25"; + case Qt.BottomLeftCorner: + return "M50,75 l 0,25 a50,50 0 0,1 -50,-50 l 25,0 a25,25 0 0,0 25,25"; + } + } + } + Text { + id: text + anchors { + centerIn: parent + horizontalCenterOffset: switch (orientation) { + case Qt.TopRightCorner: + case Qt.BottomRightCorner: + return 25; + default: + return -25; + } + verticalCenterOffset: switch (orientation) { + case Qt.BottomLeftCorner: + case Qt.BottomRightCorner: + return 25; + default: + return -25; + } + } + horizontalAlignment: Text.AlignHCenter + rotation: switch (orientation) { + case Qt.TopRightCorner: + case Qt.BottomLeftCorner: + return 45; + default: + return -45; + } + } + } + + PieSegment { + orientation: Qt.TopLeftCorner + text: labels[0] + } + PieSegment { + orientation: Qt.TopRightCorner + text: labels[1] + } + PieSegment { + orientation: Qt.BottomRightCorner + text: labels[2] + } + PieSegment { + orientation: Qt.BottomLeftCorner + text: labels[3] + } + } +} diff --git a/examples/quick/pointerhandlers/pieMenu.qml b/examples/quick/pointerhandlers/pieMenu.qml new file mode 100644 index 0000000000..c00f5731be --- /dev/null +++ b/examples/quick/pointerhandlers/pieMenu.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import "components" + +Item { + width: 800 + height: 480 + + Rectangle { + id: rect + anchors.fill: parent; anchors.margins: 40 + color: pieMenu.active ? "lightgrey" : "darkgrey" + + QuadPieMenu { + id: pieMenu + labels: [ "whiz", "bang", "fizz", "buzz" ] + onTriggered: (text)=> feedback.text = "selected **" + text + "**" + onCanceled: feedback.text = "canceled" + } + + Text { + id: feedback + x: 6; y: 6 + textFormat: Text.MarkdownText + text: "hold for context menu" + } + } +} diff --git a/examples/quick/pointerhandlers/pointerhandlers.qml b/examples/quick/pointerhandlers/pointerhandlers.qml index ba3eb27132..a5cbbbd56a 100644 --- a/examples/quick/pointerhandlers/pointerhandlers.qml +++ b/examples/quick/pointerhandlers/pointerhandlers.qml @@ -65,6 +65,7 @@ Rectangle { Component.onCompleted: { addExample("tap", "TapHandler: device-agnostic tap/click detection for buttons", Qt.resolvedUrl("tapHandler.qml")) addExample("multibuttons", "TapHandler: gesturePolicy (99 red balloons)", Qt.resolvedUrl("multibuttons.qml")) + addExample("pieMenu", "TapHandler: pie menu", Qt.resolvedUrl("pieMenu.qml")) addExample("single point handler", "PointHandler: properties such as seat, device, modifiers, velocity, pressure", Qt.resolvedUrl("singlePointHandlerProperties.qml")) addExample("hover sidebar", "HoverHandler: a hierarchy of items sharing the hover state", Qt.resolvedUrl("sidebar.qml")) addExample("joystick", "DragHandler: move one item inside another with any pointing device", Qt.resolvedUrl("joystick.qml")) diff --git a/examples/quick/pointerhandlers/tapHandler.qml b/examples/quick/pointerhandlers/tapHandler.qml index 90cf778ede..549df89021 100644 --- a/examples/quick/pointerhandlers/tapHandler.qml +++ b/examples/quick/pointerhandlers/tapHandler.qml @@ -49,6 +49,7 @@ ****************************************************************************/ import QtQuick +import QtQuick.Layouts import "components" Item { @@ -57,7 +58,7 @@ Item { Rectangle { id: rect - anchors.fill: parent; anchors.margins: 40 + anchors.fill: parent; anchors.margins: 40; anchors.topMargin: 60 border.width: 3; border.color: "transparent" color: handler.pressed ? "lightsteelblue" : "darkgrey" @@ -68,9 +69,10 @@ Item { (rightAllowedCB.checked ? Qt.RightButton : Qt.NoButton) gesturePolicy: (policyDragThresholdCB.checked ? TapHandler.DragThreshold : policyWithinBoundsCB.checked ? TapHandler.WithinBounds : + policyDragWithinBoundsCB.checked ? TapHandler.DragWithinBounds : TapHandler.ReleaseWithinBounds) - onCanceled: { + onCanceled: function(point) { console.log("canceled @ " + point.position) borderBlink.blinkColor = "red" borderBlink.start() @@ -170,11 +172,10 @@ Item { } } - Row { - spacing: 6 + GridLayout { + columnSpacing: 6; rowSpacing: 6 Text { text: "accepted mouse clicks:" - anchors.verticalCenter: leftAllowedCB.verticalCenter } CheckBox { id: leftAllowedCB @@ -189,9 +190,12 @@ Item { id: rightAllowedCB text: "right" } + Text { - text: " gesture policy:" - anchors.verticalCenter: leftAllowedCB.verticalCenter + text: "gesture policy:" + horizontalAlignment: Text.AlignRight + Layout.row: 1 + Layout.fillWidth: true } CheckBox { id: policyDragThresholdCB @@ -199,6 +203,7 @@ Item { onCheckedChanged: if (checked) { policyWithinBoundsCB.checked = false; policyReleaseWithinBoundsCB.checked = false; + policyDragWithinBoundsCB.checked = false; } } CheckBox { @@ -207,6 +212,7 @@ Item { onCheckedChanged: if (checked) { policyDragThresholdCB.checked = false; policyReleaseWithinBoundsCB.checked = false; + policyDragWithinBoundsCB.checked = false; } } CheckBox { @@ -216,6 +222,16 @@ Item { onCheckedChanged: if (checked) { policyDragThresholdCB.checked = false; policyWithinBoundsCB.checked = false; + policyDragWithinBoundsCB.checked = false; + } + } + CheckBox { + id: policyDragWithinBoundsCB + text: "drag within bounds" + onCheckedChanged: if (checked) { + policyDragThresholdCB.checked = false; + policyWithinBoundsCB.checked = false; + policyReleaseWithinBoundsCB.checked = false; } } } |