I want to drag my custom buttons QML after a long press over them. I've implemented that behaviour, however the problem is that after enabling drag, I need to press button once again to actually start dragging. How should I implement this mechanism if I want to move buttons without releasing after long press?
Here is my button code (onReleased and onLongPressed are my own signals):
ButtonWidget.SoftButtonUI
{
id:softButtonDelegate2
x:500
y:300
labelText: "button"
iconImageSource: path
isGrayedOut: false
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
onReleased:
{
console.log("onClicked")
}
onLongPressed:
{
console.log("onLongPressed")
dragArea2.enabled = true
}
MouseArea {
id: dragArea2
enabled: false
anchors.fill: parent
drag.target: parent
onReleased: parent.Drag.drop()
onClicked: {
console.log("MouseArea onClicked")
}
onPressAndHold: {
console.log("MouseArea onPressAndHold")
}
}
}
Any idea?
Generally speaking you can connect different signals and concatenate operations as discussed in this page. You should have a look at it since it is full of nice and useful information.
However, when it comes to mouse events, an interesting approach to events concatenation is given by MouseEvents acceptation. Documentation says about MouseEvent::accepted:
Setting accepted to true prevents the mouse event from being
propagated to items below this item. Generally, if the item acts on
the mouse event then it should be accepted so that items lower in the
stacking order do not also respond to the same event.
In this case we can take the opposite approach by not accepting the event. This way the pressed event can be used to both activate the drag and actually perform it. Then the MouseEvent can be accepted (implicitly) in the release event, occurring at the end of the drag.
Here is a simple example following this approach. As the mouse is pressed and hold the drag.target is set and drag can start, whereas when the mouse is released the drag.target is removed, removing the dragging behaviour. To test it, just press and hold the mouse over the rectangle and when it changes color just drag it.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 300
height: 300
visible: true
Rectangle {
id: item
border.width: 2
x: 100
y: 100
width: 100
height: 100
state: "BASE"
states: [
State {
name: "BASE"
PropertyChanges { target: mouseArea; drag.target: undefined}
PropertyChanges { target: item; color: "steelblue"}
},
State {
name: "DRAGGABLE"
PropertyChanges { target: mouseArea; drag.target: item}
PropertyChanges { target: item; color: "darkblue"}
}
]
MouseArea {
id: mouseArea
anchors.fill: parent
drag{
// target: NOT SET HERE
minimumX: 0
minimumY: 0
maximumX: parent.parent.width - parent.width
maximumY: parent.parent.height - parent.height
smoothed: true
}
onPressAndHold: {
item.state = "DRAGGABLE"
mouse.accepted = false // mouse event is USED but NOT CONSUMED...
}
onReleased: {
item.state = "BASE" // mouse event acceptation occurs here!
}
}
}
}
This simple approach should work perfectly also with your custom signals.
Related
I have a QML Controls 2 SplitView and a redefined handle, which works well, but I want detect mouse release event on the handler, so I could collapse the SplitView under a certain threshold of width. Adding a MouseArea on top of the existing handle will absorb drag events, so I'm unable to move the handlebar. Any idea how could I gather the mouse release event, or any other solution which solves this problem?
Alright, I have created an example application. As you can see in this example, my MouseArea is marked with yellow and collapses the right view programmatically when double clicked, which is nice, but I also want to drag the handlebar and upon mouse release under a certain width threshold I want to collapse the view as well. The black part of the handlebar where my MouseArea is not covering the handlebar, responds to drag, but since there is no signal I can gather from it, the width threshold already set shouldCollapse boolean property, so the view won't update. Probably I could solve this issue with a timer, but I need a more sophisticated solution.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 800
height: 400
visible: true
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
function toggleCollapse() { collapsibleRect.shouldCollapse = !collapsibleRect.shouldCollapse }
handle: Rectangle {
implicitWidth: 20
implicitHeight: 20
color: "black"
MouseArea {
anchors.centerIn: parent
width: parent.width
height: parent.height / 2
onDoubleClicked: splitView.toggleCollapse()
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
anchors.centerIn: parent
text: "Double click to collapse"
rotation: 90
}
}
}
}
Rectangle {
id: mainRect
color: "green"
SplitView.fillWidth: true
Text {
anchors.centerIn: parent
font.pixelSize: 24
text: "Main scene"
}
}
Rectangle {
id: collapsibleRect
property bool shouldCollapse: false
SplitView.preferredWidth: shouldCollapse ? 0 : 300
color: "purple"
clip: true
onWidthChanged: {
if(width < 200) shouldCollapse = true
else shouldCollapse = false
}
Text {
anchors.centerIn: parent
rotation: parent.shouldCollapse ? 90 : 0
font.pixelSize: 24
text: parent.shouldCollapse ? "SHOULD BE COLLAPSED" : "NOT COLLAPSED"
Behavior on rotation { NumberAnimation { duration: 100 } }
}
}
}
}
I had a similar problem and was able to solve it thanks to the hint of #Ponzifex that the SplitView's resizing property will be set to true as soon as the handle is clicked. Using a Timer I managed to detect whether the handle was quickly pressed twice in a row.
SplitView {
id: view
...
handle: Rectangle {
...
}
//============================================================
// double click behavior
Timer {
id: doubleClickTimer
interval: 300 // number of ms between clicks that should be considered a double click
}
property bool doubleClicked: false
// `resizing` will be set to true even if the handle is just pressed
onResizingChanged: {
if (view.resizing) {
if (!doubleClickTimer.running) {
doubleClickTimer.start();
return;
}
view.doubleClicked = true;
} else {
if (view.doubleClicked) {
// do any manual resizing in here
view.doubleClicked = false;
}
}
}
}
It is important to note, however, that it is only possible to resize the contents of a SplitView when resizing is false. That's why I need to have the doubleClicked helper property.
Add this to MouseArea:
onPressed: {
mouse.accepted = (mouse.flags & Qt.MouseEventCreatedDoubleClick);
}
propagateComposedEvents: true
cursorShape: Qt.SplitHCursor
Hopefully I'm not missing something obvious here.
I am writing an app and have made a zoom in button with an Image and a MouseArea. I need the button to repeat a method call after, say, every second to zoom in while holding the mouse button down. It isn't entirely obvious how to make this repeat. Right now I have:
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
onPressed: { cameraController.zoomIn(0.5); }
}
I have also tried with
onPressAndHold: { cameraController.zoomIn(0.5); }
which does basically the same, although with a small delay as expected, but I need to repeat this action every second while the mouse button is held.
To perform the task you need you must use a Timer. the timer must remain active while the containsMouse is active. you must also enable triggeredOnStart to run immediately if the timer is activated.
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
}
Timer {
id: timer
interval: 1000
repeat: true
triggeredOnStart: true
running: zoomIn.containsMouse
onTriggered: cameraController.zoomIn(0.5) //task
}
}
}
An inner MouseArea gets the mouse events first. I would like to "see" these events, so as to set various properties, but not affect them. I would like the mouse events to propagate to any parent MouseArea.
consider this code. I would like clicking on the blue square to see "blue pressed" and "blue released" as well as passing to "parent pressed" and "parent released".
If i accept the event, the parent does not get it. if i don't accept pressed, then i do not see released.
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow
{
visible: true
width: 800
height: 1024
Rectangle
{
anchors.fill: parent
color: "yellow"
MouseArea
{
// i want these to happen even when mouse events are in the
// blue square
anchors.fill: parent
onPressed: console.log("parent pressed");
onReleased: console.log("parent released");
}
Rectangle
{
x: 100
y: 100
width: 100
height: 100
color: "blue"
// i would like to "see" events, but not affect them
// i want all mouse events to pass to parent, as if i am not here.
// however, not accepting "pressed" means i don't see "released"
MouseArea
{
anchors.fill: parent
onPressed:
{
console.log("blue pressed");
mouse.accepted = false
}
onReleased:
{
console.log("blue released");
mouse.accepted = false
}
}
}
}
}
ideas welcome. thanks,
If propagateComposedEvents is set to true, then composed events will be automatically propagated to other MouseAreas in the same location in the scene. Each event is propagated to the next enabled MouseArea beneath it in the stacking order, propagating down this visual hierarchy until a MouseArea accepts the event. Once the event has traveled down the hierarchy there no way for it to come up the hierarchy until another mouse event occur. So, when you are setting mouse.accepted = false in the blue rectangle the mouse event goes to the yellow rectangle and it receives both pressed and released signals but the upper rectangle will no longer receive any events untill another mouse event occurs. So, the answer is NO.
If you want to handle mouse events on different levels such as if you want one MouseArea to handle clicked signals and the other to handle pressAndHold, or if you want one MouseArea to handle clicked most of the time, but pass it through when certain conditions are met following example would help
import QtQuick 2.0
Rectangle {
color: "yellow"
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: console.log("clicked yellow")
}
Rectangle {
color: "blue"
width: 50; height: 50
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("clicked blue")
mouse.accepted = false
}
}
}
}
Is it possible to propagate a MouseArea's positionChanged event to an underlying one?
I've tried to set the mouse.accepted to false for any existing signal handler of the top-most MouseArea as well as setting the propagateComposedEvents to true. Neither of those worked (although I'm not surprised with the propagateComposedEvents not working since the documentation says it only relays events like clicked, doubleClicked and pressAndHold).
Depending on your structure you can always manually propagate the event by having your onPositionChanged handler call underlyingMouseArea.positionChanged(mouse) This should manually emit the signal in the underlying MouseArea. My only concern is that you might not be able to pass a MouseEvent object this way(never tried with anything other than a string). However, you can always perform this manual emit in C++ which definetly would not suffer from any type conversion issues.
Unless you need to handle position change events with multiple mouse areas simultaneously you could try reparent your top mouse area:
import QtQuick 2.2
import QtQuick.Layouts 1.1
Rectangle {
id: __root
color: "lightgreen"
width: 360
height: 360
Rectangle {
id: rect2
width: 100; height: 100
color: "cyan"
MouseArea {
parent: __root // set 'logical' parent
anchors.fill: rect2 // set 'visual' ancestor
hoverEnabled: true
onPositionChanged: {
console.log('mouse area 2 onPositionChanged');
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log('mouse area 1 onPositionChanged');
}
}
}
There is an unresolved bugreport.
I have 9:9 matrix of Rectangle elements on the main QML form with Repeater. What I want to implement is if user clicks on one of rectangles, it zooms to TextEdit widget which on Esc press zooms back.
Is it possible with QML?
If yes, how am I supposed to turn Rectangle to TextEdit and zoom this TextEdit to fill the parent?
Just starting to work with QML and can't quite get an answer from http://doc.qt.nokia.com/4.7-snapshot/qdeclarativeanimation.html yet.
Thank you.
1) Sure thing! This is more or less what QML is made for.
2) This is an example of how you can do what you want (not the only way to do it):
Rectangle {
id: parentRect
width: 500; height: 500
// Every item in the grid should look like this
Rectangle {
id: singleItem
color: "red"
state: "closed"
// Hidden text input, shown when user clicks
TextInput {
id: textInput
anchors.fill: parent
text: "Input here"
cursorVisible: true
}
// MouseArea that will catch the user click
MouseArea {
anchors.fill: parent
onClicked: singleItem.state = "open"
}
// Item states
states: [
State {
name: "closed"
PropertyChanges {target: singleItem; width: 25; height: 25}
PropertyChanges {target: textInput; opacity: 0}
},
State {
name: "open"
PropertyChanges {target: singleItem; width: parentRect.width; height: parentRect.height}
PropertyChanges {target: textInput; opacity: 1; focus: true}
}
]
// Transitions between states
transitions: Transition {
ParallelAnimation {
NumberAnimation {
target: singleItem
properties: "width,height"
duration: 1000
}
NumberAnimation {
target: textInput
property: "opacity"
duration: 1000
}
}
}
}
}
Even I'm new to qt-quick. I don't think it is possible to zoom unless we write our code to do such. I'm not sure though. :-)
This effect is good and it will b nice to see in coming versions. Try to give a feature request to the community <3