aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2021-10-07 22:27:32 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2021-12-02 12:52:58 +0100
commite17bfffc075202ff9ee8fba0f378f95037514740 (patch)
treee5c380c95731fc2aadc8d032a8a9ecb1a3284039 /examples
parent06e68e0cc06e5c53654bf1d9fa17376317640bc8 (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.qml158
-rw-r--r--examples/quick/pointerhandlers/pieMenu.qml77
-rw-r--r--examples/quick/pointerhandlers/pointerhandlers.qml1
-rw-r--r--examples/quick/pointerhandlers/tapHandler.qml30
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;
}
}
}