Keep cursor shape while pressed as it moves outside its MouseArea - qt

I am implementing narrow resize handles which give me annoying behavior. The cursor shape is as expected while the mouse is directly over the handle, but once dragging the handle is initiated, the cursor shape becomes inconsistent. There are two causes of this:
when the cursor moves fast and goes ahead of the handle until the handle "catches up" (or when "fluid qml" is too fluid) - this is particularly nasty as the cursor shape rapidly changes and blinks
when the cursor moves outside of the allowed degree of freedom for the handle
I looked up the doc but it doesn't seem to contain anything about locking the cursor until the press is released.
I did manage to find a hack to fix it - using a dummy overlay MouseArea with acceptedButtons: Qt.NoButton - this actually helps to fake cursor consistency, but comes with an issue of its own. Having that overlay mouse area doesn't allow the cursor to change to a resize shape when it is over the handle, since the handle is under the overlay mouse area it doesn't get to modify the cursor shape at all. So the resize shape kicks in only after the handle is clicked, which is far from ideal. Setting the overlay mouse area to enabled: false doesn't change that - it still keeps blocking cursor shape changes from underlying mouse areas. There is a workaround for that as well, for example setting the overlay mouse area size to 0x0, but it is kind of ugly.
Ideally, the cursor shape should persist until the mouse area is pressed, regardless of whether it is in or outside of its area - after all, the press is not released if you go outside of it, thus the mouse area is still in control and should persist its cursor shape. For example - the window resize handles remain resize shape even if it is moved to resize the window smaller than its minimal size, until the press is released.
To me it seems that there are flaws in the implementation of MouseArea - the cursor shape is not kept while pressed, and the cursor shape is changed even if the mouse area is disabled.

I didn't find a way to do this out-of-the-box, it is pretty easy to create a helper for this though. On the qml side you can e.g. have:
CursorChanger {
cursor: Qt.SizeHorCursor
active: dragArea.containsMouse || dragArea.drag.active
}
On the C++ side you'll need a helper class like this:
class CursorChanger : public QObject
{
Q_OBJECT
Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
Q_PROPERTY(int cursor READ cursor WRITE setCursor NOTIFY cursorChanged)
// ...
}
In the implementation you can use QGuiApplication::setOverrideCursor and QGuiApplication::restoreOverrideCursor to actually set/reset the cursor. Don't forget to also reset in the CursorChanger destructor if active at that point. If you then register the type:
qmlRegisterType<CursorChanger>(uri, 1, 0, "CursorChanger");`
You can use this type from qml.

I think there are some use cases for the current behavior. For instance, the cursor could convey some relationship between the currently hovered object and the one where the mouse was pressed. Another example, a drag could have its speed intentionally limited, and when the user goes too fast there is a consequence that depends on the item behind.
dtech's demand is more common for sure, and I also would like to see that as an optional feature, but not as a change. The way it is now provides more versatile pieces for apps. I don't like polished components that can be used only exactly as the library writer imagined.
Another QML only solution for a persistent drag cursor is to have a MouseArea behind all itens to hold the persistent shape when needed:
Item
{
id: scene; width: 800; height: 600
MouseArea
{
id: mouse
anchors.fill: scene
}
Rectangle
{
id: draggable; width: 40; height: 30; color: "red"
MouseArea
{
anchors.fill: draggable
drag.target : draggable
//set and unset a persistent cursor
onPressed : mouse.cursorShape = Qt.DragMoveCursor;
onReleased: mouse.cursorShape = Qt.ArrowCursor; //QT default cursor
//let non default scene cursors prevail over the item's
cursorShape: mouse.cursorShape === Qt.ArrowCursor ?
Qt.OpenHandCursor : mouse.cursorShape;
}
}
}

Related

How to determine whether a QML component is currently visible on screen

I have a set of controls which have bindings to frequently changing data values. The data comes from a limited hardware-bus. Therefore, it would be better to disable the binding while the control is not visible on screen. The item's visible-property doesn't help in this case. So, how to determine if an Item-based QML widget is currently visible on screen (and not hidden by an overlay or currently outside the visible area)?
Source: https://forum.qt.io/topic/54116/how-to-check-if-a-item-is-currently-visible-on-screen
I have almost the same problem. Hoping someone here has a solution.
Here's what I would try to get working:
First, I presume there is a ScrollView or Flickable in play here? If so, then hook to signals like Flickable::movementEnded().
Second, when that signal fires, use Item::mapToItem() to check if each of your Item's visible rectangle (based on x, y, width, height) intersects your window's contentItem rectangle. Set the result as a boolean on each of your items and make sure the data retrieval is disabled when it is false (using && or a tertiary JS expression).
Or if more convenient, remove the binding when false and reapply it with Qt.binding() when true.

How to scroll with arrow keys in a Kirigami ScrollablePage?

The Qt QML based mobile/desktop convergent UI framework Kirigami provides a QML type ScrollablePage to support scrolling through content. Placing any visual QML item into it automatically makes it scrollable if it's larger than the ScrollablePage itself:
ScrollablePage is a Page that holds scrollable content, such as ListViews.
Scrolling and scrolling indicators will be automatically managed.
Kirigami.ScrollablePage {
id: root
//The rectangle will automatically be scrollable
Rectangle {
width: root.width
height: 99999
}
}
(source)
This provides scrollbars and allows scrolling with the mouse wheel, two-finger-scrolling with the touchpad and flicking ("click and throw") scrolling as we're used to from touchscreen devices.
However, it does not allow scrolling with any keyboard keys (Arrow Up / Down, Page Up / Down). How can I make that possible? The usual approach of doing Keys.onUpPressed: scrollBar.decrease() does not work because the ScrollablePage's scrollbar is not accessible as part of its public API.
Instructions
Use a Flickable to wrap the content items you put into your ScrollablePage. Then evaluate key press events in the Flickable and in response execute flick() to scroll the view. Example (combining examples from the Kirigami manual and from the Qt manual):
Kirigami.ScrollablePage {
id: root
Flickable {
focus: true
topMargin: 20; leftMargin: 20; bottomMargin: 20; rightMargin: 20
Keys.onUpPressed: flick(0, 800)
Keys.onDownPressed: flick(0, -800)
Rectangle {
width: root.width
height: 5000
}
}
}
Details and Explanation
While you can't access the scrollbar, you can access what the scrollbar uses to move the view: a Flickable instance. You just have to wrap it around the page's content. If you don't, ScrollablePage internally uses ScrollView to wrap your page's content in a Flickable anyway, but then you don't have a reference on it to execute flick().
Executing flick() does the same as when the user flicks the element, so the scrollbar position etc. will be updated alright.
If it still does not work, then (1) maybe you give too small pixel/second values to Flickable::flick() for scrolling to be visible or (2) maybe the initial Flickable::flickDeceleration values on your platform are messed up. These values are platform specific, so it can require some experimentation. On some platforms, setting them to zero during a flick() will help, while under Linux this is exactly the value preventing any scroll movement.
It is not necessary to enable ScrollablePage::keyboardNavigationEnabled for the above solution to work, since that is only for moving the currentItem of suitable content with the arrow keys (see below), and not for scrolling in general. It will even prevent ordinary scrolling in case your page content is an item view (ListView, GridView etc.).
Alternative solution for item views
If the content of your ScrollablePage is an item view (any QML object that has a currentItem property, such as ListView or GridView), then instead of wrapping that content in a Flickable just enable ScrollablePage::keyboardNavigationEnabled. It will allow you to move the currentItem with the Arrow Up and Arrow Down keys. That's what one usually wants for these views, even though it's not scrolling but rather keyboard navigation.

QML mouseArea losing focus on releasing mouse button

In my program you have polygons on screen, and you can drag their sides, the verticies, or the whole polygons around. I want to show the user which will happen with the mouse in its current position. For that I need the onPositionChanged event handler of the mouseArea this is happening in.
The dragging works fine, the problem is that the mouseArea loses focus the moment the mouse is released on it, so I'm not able to call my onPositionChanged function when the user is not pressing a mouse button.
HoverEnabled is set to true in my mouseArea. It seems some other object keeps stealing focus, but I set preventStealing to true, set focus to true, and looked for the word "focus" in the whole project, and removed every line that would steal focus There are also no focusScopes in the project.
example code:
MouseArea
{
hoverEnaled: true
focus: true
preventStealing: true
...
onPositionChanged:
{
doStuff();
//Works fine while mouse button is pressed. Doesn't get called when it's released
}
Component.onCompleted: forceActiveFocus();
...
}
I figured it out!
Before getting to my polygon screen, there was a popup in the program, that was closed improperly (instead of popup.close(), popup.visible = false; popup.destroy(); was used). Closing it the proper way solved my problem.

How to disable clicking through item in qml?

The application I currently work on has a map as a background, and above it various other dialogs(views) with more than one view inside can be opened. When some of the dialogs is active, when dragging over it's background map is moving like there's nothing above it. Does someone know how to disable this? I don't want map to react on clicks or anything inside a dialog.
The project is organised so that each dialog is implemented in separate qml file:
I have each qml file for each dialog, and each component of application (map), so
when you click, for example on settings tab in scrollable horizontal list, settings tab is opened from qml that holds all dialogs, including bottom and top of the app
each dialog is above map and has a 50% transparent background, with related images and buttons in it
I want to disable dragging map while dragging over dialog's background. I tried with setting this to each dialog:
MouseArea {
anchors.fill: parent
onClicked: mouse.accepted = true
}
(parent is Item that holds all elements of a dialog), but this doesn't work.
If I'm understanding your question correctly, it should suffice to set the MouseArea's propagateComposedEvents to false.

Qt Quick: How to handle hover-type events in MouseArea, yet let lower-z MouseAreas handle them too?

If, on my MouseArea, I've enabled the property hoverEnabled, I'm notified of the entered signal, even if no mouse button is held at that time (just as I want).
But then a lower-z MouseArea will not receive positionChanged events on the first MouseArea's space, since they're eaten up by the first MouseArea.
The Qt docs on positionChanged specifically say this:
When handling this signal, changing the accepted property of the mouse parameter has no effect.
Example code:
Item {
width: 100
height: 100
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("MouseArea 1");
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
console.log("MouseArea 2");
}
}
}
"MouseArea 1" is never printed. I want both strings to be printed.
Is there a way to do it?
Related question note: This question is very similar but the asker there has 2 differently-sized overlapping MouseAreas, and he wants the smaller one (which is has higher z) to eat up events when the events fall within both. I want both overlapping MouseAreas to handle events that fall within both.
At a first glance, it doesn't look like this is possible, most likely because an implementation detail limitation. Normally, it is effortless to propagate events, what you need is to propagateComposedEvents: true and to mouse.accepted = false in the handler.
However, while that works as expected for clicks, it doesn't work for onEntered. My first thought was that it was the lack of the mouse signal parameter for the entered signal, however positionChanged has it but it still doesn't work as expected, as the documentation says, it has no effect. And that's actually true for positionChanged unlike for clicked, for which the documentation also says should have no effect but it most certainly does.
Furthermore, even if you make the top mouse area smaller, you will be able to register the cursor entering for both, but position changes will only register for the top-most mouse area that is under the cursor.
I wouldn't say that such functionality is an unreasonable feature, so you might want to file a bug report with a request to fix it so that you can get "common sense" behavior. However, such a fix will likely not come any time soon, if ever.
Depending on your actual usage scenario there might be some workarounds possible.
Worst case scenario, you can always have one single mouse area for the entire application window and manually propagate events to underlying objects, it is really not as tedious as it may sound, various similar limitations in the way things work in QML has driven me to do exactly that for both mouse and keyboard events.

Resources