How to get rid of handler dragging delay? - qt

There's a range slider implementation. The problem is that when I press on a handler and start dragging, it doesn't move for a moment, therefore it happens some delay and distance between a handler and a mouse cursor.
Image { // handler's implementation
id: handler1
// ...
MouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAxis
drag.minimumX: -width
drag.maximumX: handler2.x - parent.width
onPositionChanged: {
slider1 = (max - min) * (handler1.fakeX) / root.width + min
}
}
}
Could you please tell me how to deal with it?

You have to set the drag.threshold property of your MouseArea to 0 so that dragging starts immediately.

Related

Qml - How to stop a rectangle dragging outside it's parent

I am using qml to create a resizable rectangle that can be dragged inside it's parent pane, but cannot be dragged outside (Fig. 1). When I resize it e.g. using the top left handle, it works great and can't be resized outside it's parent (Fig. 2). But when I drag the entire rectangle, it can be dragged outside its parent (Fig. 3). My code attempts to use the same logic for both, i.e. once the rectangle's left side reaches it's parent's left side, it cancels the drag. Works fine for resizing, but doesn't when I drag the rectangle. What am I doing wrong?
Pane {
id: compPane
implicitHeight: imageListModel.reqViewHeight
implicitWidth: imageListModel.reqViewWidth
leftPadding: 0
topPadding: 0
clip: true
background: Rectangle{
id: backgroundRect
anchors.fill: parent
color: "gray"
border.color: "black"
border.width: 6
}
// Resizable rectangle that can be dragged
Rectangle {
id: indRect
x:parent.width / 4
y: parent.height / 4
width: parent.width / 2
height: parent.width / 2
border {
width: 2
color: "steelblue"
}
color: "#354682B4"
MouseArea {
id: indImagesDragArea
anchors.fill: parent
anchors.margins: 8
drag.target: parent
onPositionChanged: {
if (drag.active){
if (indRect.x <= 0 + backgroundRect.border.width)
Drag.cancel()
if (indRect.x + indRect.width >= (compPane.width - backgroundRect.border.width) )
Drag.cancel()
}
}
}
// Top Left handle - works well
Rectangle {
width: 15
height: 15
color: "steelblue"
anchors.verticalCenter:parent.top
anchors.horizontalCenter: parent.left
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeFDiagCursor
drag{ target: parent; axis: Drag.XAndYAxis }
onPositionChanged: {
if(drag.active){
//var delta = Math.max(mouseX, mouseY)
var newWidth = indRect.width - mouseX
var newHeight = indRect.height - mouseY;
if (newWidth < width || newHeight < height)
return
if (indRect.x <= 0 + backgroundRect.border.width && mouseX < 0){
Drag.cancel()
indRect.x = 0 + backgroundRect.border.width
}
else {
indRect.width = newWidth
indRect.x = indRect.x + mouseX
}
if (indRect.y <= 0 + backgroundRect.border.width && mouseY < 0){
Drag.cancel()
indRect.y = 0 + backgroundRect.border.width
}
else{
indRect.height = newHeight
indRect.y = indRect.y + mouseY
}
}
}
}
}
}
}
Drag.cancel() is actually intended for use when implementing drag and drop types of interactions. In this case, the calls to it are doing nothing because you are never actually starting one of those sequences. Your upper left handle example works fine if you comment out the calls to Drag.cancel().
Why your upper left example is working is because you are limiting the x and y on position updates to clip them to the scene via explicit updates of indRect.x and indRect.y. You just need to implement the same technique for the main drag scenario.
For instance:
onPositionChanged: {
if (drag.active){
if (indRect.x <= 0 + backgroundRect.border.width)
indRect.x = 0 + backgroundRect.border.width;
if (indRect.x + indRect.width >= (compPane.width - backgroundRect.border.width) )
indRect.x = compPane.width - backgroundRect.border.width - indRect.width;
}
}
You could try to use the property of drag.minimum and drag.maximum.
See the documentation:
https://doc.qt.io/qt-5/qml-qtquick-mousearea.html#drag.threshold-prop

Qt EventFilter - Once Event consumed by event filter, control never returns back to MouseArea

I am trying to filter out touch events from multiple touch points. So have installed an EventFilter that react and consumes events with multiple touch points else to return false. When I ran the sample qml, the mouse click initially works with single touch on window and I do multiple touch on the window the event get consumed by the event filter(as expected) but then when I do single touch on window again the MouseArea never gets the event. I have also tried with debug on return of event filter when indeed returns false for single touch point. Qt Version-> 5.9.4 MingW 32 bit on Windows
bool MultipleTouchFilter::eventFilter(QObject *obj, QEvent *event)
{
qDebug() <<"Object name " << obj->objectName();
QTouchEvent * eventTouch = dynamic_cast< QTouchEvent * >(event);
if(eventTouch != nullptr && eventTouch->touchPoints().count() > 1)
return true;
else
return false;
}
I've installed this filter to the QCoreApplication
QCoreApplication *app = QCoreApplication::instance();
if (app) {
app->installEventFilter(m_multitouchFilter);
}
main.qml
Window {
objectName: "main window"
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea{
objectName: "MouseArea"
anchors.fill: parent
onClicked: {
console.log("Single touch ")
}
}
}

MenuBar like behaviour in QML

I want to have menubar like behaviour when clicked on a rectangle. Whenever a rectangle is clicked a model is updated and a ListView is shown. I want this ListView to disappear whenever another Rectangle is clicked and the listmodel should not be appended with each click. Here is my sample code.
Card.qml
Rectangle {
id: card
width: 50
height: 100
color: "pink"
Item {
id: rec
width: 50
anchors.bottom: parent.top
ListModel {
id: menuListModel
}
Component {
id: delegate
Rectangle {
width: 50
height: 20
color: "blue"
Text {
anchors.fill: parent
anchors.centerIn: parent
text: commandText
}
}
}
ListView {
anchors.fill: parent
model:menuListModel
delegate: delegate
interactive: false
}
}
MouseArea {
anchors.fill: parent
onClicked: {
rec.height += 40;
menuListModel.append({"commandText" : "Act"});
menuListModel.append({"commandText" : "Set"});
}
}
}
main.qml
Item {
width: 120
height: 200
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Card {
id: card1
}
Card {
id: card2
}
}
}
Also I want to call certain function upon clicking menu buttons i.e. Act and Set.
Edit
The following function is called with appropriate flags when a card(here rectangle) is clicked.
property int command_activate: 0x0001
property int command_summon: 0x0002
property int command_spsummon: 0x0004
property int command_mset: 0x0008
property int command_sset: 0x0010
property int command_repos: 0x0020
property int command_attack: 0x0040
property int command_list: 0x0080
function showMenu(flag) {
if(flag & command_activate) {
rec.height += 15;
menuListModel.append({"commandText" : "Activate"});
}
if(flag & command_summon) {
rec.height += 15;
menuListModel.append({"commandText" : "Normal Summon"});
}
if(flag & command_spsummon) {
rec.height += 15;
menuListModel.append({"commandText" : "Special Summon"});
}
if(flag & command_mset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_sset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_repos) {
if(position & pos_facedown) {
rec.height += 15;
menuListModel.append({"commandText" : "Flip Summon"});
}
else if(position & pos_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "To Defense"});
}
else {
rec.height += 15;
menuListModel.append({"commandText" : "To Attack"});
}
}
if(flag & command_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "Attack"});
}
if(flag & command_list) {
rec.height += 15;
menuListModel.append({"commandText" : "View"});
}
}
So, in short when a card is clicked a menu has to be shown according to the flag on the top of the card.
There are several problems with your code.
You cannot name your delegate "delegate". When you do this, the ListView uses its own delegate property to set itself, leading to nothing happening.
Also, why don't you just statically fill your ListView then use the visible property to toggle whether you display it or not? If you want it to disappear whenever another Card is clicked, you may have to use the focus property.
Indeed, setting focus to true will reset the focus of all other Items within the focus scope. Something like this might work:
Rectangle {
id: card
...
property alias model: list.model
Component {
id: mydelegate
Rectangle {
width: 50
height: 20
...
}
}
ListView {
id: list
visible: card.activeFocus
anchors.bottom: parent.top
width: card.width
delegate: mydelegate
interactive: false
height: childrenRect.height
}
MouseArea {
anchors.fill: parent
onClicked: {
card.focus = !card.focus
}
}
}
As for calling a function, you could add the name of the function to call directly in the ListModel. Add a MouseArea in your delegate, and send a signal on clicked. Then, you just have to call the matching slot (Agreed, the this[slot]() syntax is a bit hacky).
In Card.qml
Rectangle {
id: card
...
property alias model: list.model
signal itemClicked(string slot)
Component {
id: mydelegate
Rectangle {
...
MouseArea
{
anchors.fill: parent
onClicked: {
if(model.slot)
itemClicked(slot)
}
}
}
}
...
}
In main.qml
...
Card
{
model: ListModel {
ListElement{commandText: "Act"; slot: "act"}
ListElement{commandText: "Set"; slot: "set"}
}
function act()
{
print("act triggered")
}
function set()
{
print("set trigggered")
}
onItemClicked: { this[slot]() }
}
...
As I can't comment, to ask for clarification, I will state my assumptions from what I understood:
You have a number of ListModel that contain the information of the menu that should pop up, when clicking on the corresponding rectangle item in the bar.
The ListView that pops up shall disappear, once you clicked any other rectangle in this bar. (Also: if something outside the bar or menu is clicked? Or an item in the bar is selected?)
I'd go with only one ListView, and update/change the model, as well as the position (e.g. margin) once a button is clicked, so you don't have multiple unused objects flying around.
To not show anything, I think it is sufficient, to set an empty model.
You can also have a list of functions.
Row {
id: row
property var f: [a, b, c]
function a() { console.log('a'); }
function b() { console.log('b'); }
function c() { console.log('c'); }
width: 300
height: 50
Repeater {
id: rep
anchors.fill: parent
delegate: button
model: 3
Rectangle {
id: button
width: 98
height: 50
border.color: 'black'
Text {
text: "a"
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: row.f[index]()
}
}
}
}
e.g. will call the function a, b, or c - depending on the index of the rectangle.
I hope, so far, I could help.

Trouble getting 2 QML files to communicate using Signals

I have 2 qml files. Toolbarbutton.qml creates a button and DockWidget.qml creates a ListView. I am trying to have the button in Toolbarbutton broadcast to DockWidget that the button has been clicked. I then want to add items to my listView.
I have been trying to use a signal to establish the communication. In the Toolbarbutton.qml, I have a Rectangle item with an ID of saveButton. Under this Rectangle Item I added a signal called addSaveHistory(). I have shortened the code so its easier to see what I am doing.
Rectangle {
id: saveButton
width: 50
height: 30
border.color: "white"
color: buttonMouseArea.containsMouse ? "grey" : "black"
//Button text
Text{
id: buttonLabel
anchors.centerIn: parent
text: "Save +"
color: "white"
}
signal addSaveHistory(string url)
MouseArea{
id: buttonMouseArea
anchors.fill: parent //anchor the mousearea to the rect area
//onClicked handles valid mouse button clicks
onClicked: {
addSaveHistory("Hello?") //dispatch the event
}
}
}
In the DockWidget.qml file I have an Item that is using the Connections component. I have shorten the code so its easier to see what I am doing.
Item{
width: 100
height: 100
objectName: "Save + History"
Rectangle {
id: rect
anchors.fill: parent
color: "#323232"
Connections {
target: saveButton //id of rectangle in ToolbarButton.qml
onAddSaveHistory: {
// this is never called
}
}
}
I can't seem to figure out why the onAddSaveHistory is never called. I have also tried using a Loader component to load the Dockwidget.qml file into Toolbarbutton.qml and then specifically call a function in Dockwidget, but that doesn't work as well.
Do I just have the wrong idea of how the signals should work? I would greatly appreciation any guidance.
Here is my main.qml file.
import QtQuick 2.2
import Painter 1.0
import "save.js" as Save
Plugin {
//executed at startup
Component.onCompleted:{
//add toolbar
alg.ui.addToolBarWidget("ToolbarButton.qml")//add a button to toolbar
alg.ui.addDockWidget("DockWidget.qml")//add dock widget
Save.log("Incremental Save Plugin has been created")
}
}
First of all, realise that the signal is declared on the QML element at the root of the ToolbarButton.qml file, i.e. the Rectangle. Only the root item forms the interface for the QML element. All child elements are hidden to the outside world. So it's customary to declare the signal near the top of the file. A common ordering is:
Item {
id: root
property real foo: 0.0
property alias bar: innerItem.bar
signal baz(url location)
Rectangle {
id: innerItem
property color bar: "red"
}
}
So in this case a simple ToolbarButton.qml that declares a signal and emits it when you click in a contained MouseArea would look like this:
import QtQuick 2.3
Rectangle {
id: root
width: buttonLabel.width + 20
height: buttonLabel.height + 20
color: "steelBlue"
// Declare signal on the button object
signal addSaveHistory(string url)
Text {
id: buttonLabel
anchors.centerIn: parent
text: "Save +"
}
MouseArea {
id: buttonMouseArea
anchors.fill: parent
onClicked: {
// emit the signal
root.addSaveHistory("Hello?")
}
}
}
Then a simple consumer of this signal, here just a Text element but can be anything might look like this:
import QtQuick 2.3
Text {
id: dockWidget
text: "Waiting for a click..."
}
But wait, you say, how does that help. Well so far there is no connection. We make that when we instantiate our reciever item. So here, the main.qml file looks like this:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
// Instantiate the ToolbarButton that emits the signal when clicked
ToolbarButton {
id: toolbarButton
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
}
// Instantiate the DockWidget (just a Text element in this example)
DockWidget {
id: dockWidget
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
// Complete the plumbing that connects the signal from item above
// with id: toolbarButton.
Connections {
target: toolbarButton
// When signal addSaveHistory is emitted,
// replace binding above with a new one
onAddSaveHistory: dockWidget.text = "Button was clicked"
}
}
}
In actual fact there is no need for the Connections element to be a child of the DockWidget. Think of the ToolbarButton and Dock widget as lego bricks that we are plumbing together at a location that has a higher level of knowledge about these items and how they should interact. If you want to though, you could wrap this up into its own more complicated custom QML component.
Furthermore, if you only have one thing caring about the signal, you don't even need the Connections element at all:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
ToolbarButton {
id: toolbarButton
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
// Add signal handler directly to emitter
onAddSaveHistory: dockWidget.text = "Button was clicked"
}
DockWidget {
id: dockWidget
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
}
}
Hope this helps.

How to drag a qquickwidget?

I have a QQuickWidget inside main window. This seems to work, however, it will shake violently when dragged.
//main.qml
Rectangle{
id: root
property point dragStart
signal moved(point offset)
MouseArea{
id: dragArea
anchors.fill: parent
onPressed: root.dragStart = Qt.point(dragArea.mouseX,dragArea.mouseY)
onMouseXChanged: move()
onMouseYChanged: move()
function move(){
var offset = Qt.point(dragArea.mouseX-root.dragStart.x, dragArea.mouseY-root.dragStart.y)
root.moved(offset)
console.log(offset)
}
}
}
//MainWindow.cpp
void MainWindow::moveQml(QPointF offset){
ui->quickWidget->move(ui->quickWidget->pos()+offset.toPoint());
}
Here is the debug output when I only drag to the bottom left, the point shouldn't have positive x:
qml: QPointF(6, 2) qml: QPointF(6, 2) qml: QPointF(-6, -1) qml:
QPointF(-6, -1) qml: QPointF(5, 1) qml: QPointF(5, 1) qml: QPointF(-5,
0) qml: QPointF(-5, 0) qml: QPointF(4, 0) qml: QPointF(4, 0) qml:
QPointF(-5, 1) qml: QPointF(-5, 1)
Oh, I see. After checking the output, I know what's wrong. When moving the mouse, the mouse usually changes its x and y. So the move() will get executed twice.
onMouseXChanged: {
var offset = Qt.point(dragArea.mouseX - root.dragStart.x, 0)
root.moved(offset)
}
onMouseYChanged: {
var offset = Qt.point(0, dragArea.mouseY - root.dragStart.y)
root.moved(offset)
}

Resources