QML drag one component as if it has the top stacking order (largest z) - user-interface

QT5.5, QML:
I am using the example "Qt Quick Examples - Drag and Drop"
In this example, you could see that if we drag the red tile "1", it appears under other tiles when we drag it.
I don't like this effect since when we drag one item, I hope it always appears on the top of whole GUI.
What I tried is:
Once the mouse is pressed, we set the item's z as the largest value. And when the mouse is released, set the z to the small value (or better we could save the original value and reset it back to its original valule)
MouseArea {
id: mouseArea
width: 64; height: 64
anchors.centerIn: parent
drag.target: tile
onPressed: {
tile.z = 100;
}
onReleased: {
tile.z = 0;
parent = tile.Drag.target !== null ? tile.Drag.target : root
}
}
But the code does not work.
Actually by using console.log, I could see the z value changes, but the dragging ghost image still appears at the bottom.
So I guess when onPressed is implemented, the drag mechanism has used its original z value and gets no chance to access the updated z value.
So any idea to have a more decent drag movement?
Thanks!

The z value of items only applies to siblings and the immediate (direct) parent:
Items with a higher stacking value are drawn on top of siblings with a lower stacking order. Items with the same stacking value are drawn bottom up in the order they appear. Items with a negative stacking value are drawn under their parent's content.
Let's use a small example to test the parent/child scenario:
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
visible: true
width: 200
height: 200
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint
Rectangle {
color: "salmon"
width: 64
height: 64
anchors.centerIn: parent
Text {
text: "1"
}
Rectangle {
color: "steelblue"
x: 32
y: 32
width: 64
height: 64
Text {
text: "2"
}
Rectangle {
color: "orchid"
x: 16
y: -16
width: 64
height: 64
z: -2
Text {
text: "3"
}
}
}
}
}
We set the z value of the third Rectangle to -2, hoping that it would go behind the first one, but since it's a child of the second and not the first, it's out of reach. This is what's happening in the drag and drop example: the items are too far apart in terms of ancestory.
To elaborate further on this, let's take DragTile.qml from the Drag and Drop example and modify it in a similar way to yours (this is a nicer way of achieving the same thing, by the way):
states: State {
when: mouseArea.drag.active
ParentChange { target: tile; parent: root }
AnchorChanges { target: tile; anchors.verticalCenter: undefined; anchors.horizontalCenter: undefined }
PropertyChanges {
target: tile
z: 100
}
}
This also won't work. To see what's going on, we can use an awesome little environment variable called QSG_VISUALIZE. Specifically, we want to use the overdraw visualisation, which gives us a nice rotating 3D box containing our scene. Even with our PropertyChanges above, you can see that the stacking order of the item is unchanged:
To ensure that the item is rendered on top of everything, you need to parent it to an item that is actually above everything. We can do this by adding an Item to the end of tiles.qml:
Item {
id: dragContainer
anchors.fill: parent
}
Add a property to the DragTile component that gives the delegate access to the container:
property Item dragParent
Then, assign the container in tiles.qml:
delegate: DragTile { colorKey: "red"; dragParent: dragContainer }
Next, modify the parent property of the ParentChange in DragTile.qml:
ParentChange { target: tile; parent: dragParent }
The end result:
Note that I intentionally excluded the "back" button, but if you wanted to make it go above that as well for some reason, you can just move dragContainer higher up the hierarchy.

Related

QML on Windows: use platform menu in dark mode, or get qt menu to jump to underlines?

Using both Qt 6.3.0 and 6.4.1, I'm trying to use a menu bar on Windows that (a) renders in dark mode and (b) lets the user jump to a menu item by typing its underlined character, just like a normal Windows app does.
If I use qt.labs platform menus, I can't find a way to make (a) happen, but (b) happens by default.
If I use qt's own menus, (a) happens naturally, but (b) does not.
I set up dark mode in main.cpp as follows (based on this post, among others):
#ifdef Q_OS_WIN
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",QSettings::NativeFormat);
if(settings.value("AppsUseLightTheme")==0){
app.setStyle(QStyleFactory::create("Fusion"));
QPalette darkPalette;
QColor darkColor = QColor(64,64,64); // QColor(45,45,45);
QColor disabledColor = QColor(127,127,127);
darkPalette.setColor(QPalette::Window, darkColor);
darkPalette.setColor(QPalette::WindowText, Qt::white);
...
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor);
app.setPalette(darkPalette);
app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
#endif
My QML code is this, for platform menus:
import Qt.labs.platform as Platform
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Platform.MenuBar {
id: menuBar
Platform.Menu {
id: fileMenu
title: qsTr("&File")
Platform.MenuItem {
id: openMenuItem;
text: qsTr("&Open...")
onTriggered: fileOpenAction.trigger();
} // open
}
}
...
Or this, for native menus:
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
menuBar: MenuBar {
id: menuBar
Menu {
id: fileMenu
title: qsTr("&File")
MenuItem {
id: openMenuItem;
text: qsTr("&Open...")
onTriggered: fileOpenAction.trigger();
} // open
}
}
...
In both cases, the "Open..." item displays with an underlined O.
With the platform menus, the menu bar and menus are resolutely black on white, even as the rest of the window goes dark (explicit changes to the parent's palette aren't seen either, and the platform menu elements don't have a palette member that can be modified directly). I can type Alt+F to display the menu, and O to select the Open item. Is there a way to make the Windows-native platform menu respect dark mode?
With qt menus, the menu turns dark in dark mode, and I can type Alt+F to display the menu, but typing O does nothing. Is there a way to make the qt menus select a menu item when its underlined character is typed?
You can change the color of your controls by modifying the palette. The modification of the palette is hierarchical. i.e. you can define the palette by setting it in the parent control and the children will inherit, or, you can define the palette in the child control.
Here's a working example:
import QtQuick
import QtQuick.Controls
Page {
background: Rectangle { color: "#444" }
palette.buttonText: "lightgreen" // menu text
palette.button: "black" // menu background
palette.mid: "grey" // menu highlighted background
palette.windowText: "lightgreen" // popup menu text
palette.window: "black" // popup menu background
palette.light: "grey" // popup menu highlighted background
palette.dark: "grey" // popup menu border
MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open...")
}
MenuItem {
text: qsTr("&Quit")
}
}
Menu {
title: qsTr("&Edit")
palette.window: "#400"
MenuItem {
text: qsTr("&Copy")
}
MenuItem {
text: qsTr("&Paste")
}
}
}
}
You can Try it Online!
References:
https://doc.qt.io/qt-6/qml-qtquick-systempalette.html
https://doc.qt.io/qt-6/qml-qtquick-palette.html
"Solved" by talamaki in the comments: Pressing the letter alone is no longer that way it's done; the proper way is to press Alt+letter, and that works with qt menus — which obey dark mode. Thanks, talamaki!

Image atlas in QML

I have an image like this one:
http://www.imagemagick.org/Usage/basics/result.gif
(it contains two rows of individual data: three square images in the upper row, and below another square and two 'empty' squares)
I want to use it inside a repeater, so that I get four image buttons, each with one of the subimages.
Is there a "texture-atlas" modules in QML?
I only found http://doc.qt.io/qt-5/qml-qtquick-sprite.html and I hope there is something that is better for my use-case.
As I said in comment I would do that using QQuickImageProvider since that's the fastest and the least memory intensive way. But QML is very flexible and you can do with it whatever you want. As for your question you can do as following:
Tile.qml
import QtQuick 2.9
Item {
id: container
property alias source: img.source
property int cellWidth: 1
property int cellHeight: 1
property int slideIndex: 0
clip: true
Image {
id: img
property int cols: img.paintedWidth / container.cellWidth
property int rows: img.paintedHeight / container.cellHeight
x: -container.cellWidth * Math.floor(container.slideIndex % cols)
y: -container.cellHeight * Math.floor(container.slideIndex / cols)
}
}
and usage:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
title: "Test"
visible: true
width: 600
height: 400
Row {
spacing: 10
anchors.centerIn: parent
Repeater {
model: 4
Tile {
width: 32
height: 32
cellWidth: 32
cellHeight: 32
slideIndex: index
source: "http://www.imagemagick.org/Usage/basics/result.gif"
}
}
}
}
The idea is to wrap the entire image into a container and clip it appropriately. Here I use index-based access to a slide, you can do that as you want.

MacOS dock-like component in QML

Using QtQuick, I have a row of 5 images in a repeater. I'd like to implement an animation on hover that's similar to the MacOS dock animation. Here's a picture for reference:
To further break it down, here's what I'm trying to accomplish. These images, on hover, should act as follows:
Hovered images expand
Neighboring images expand, but slightly less
Images don't overlap on expansion
Here is the code I have so far
Row {
spacing: 2
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
id: iconRepeater
model: iconColors()
Image {
source: "icons/" + modelData + ".png"
scale: mouseArea.containsMouse ? 1.5 : 1.0
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: endTimer()
}
Behavior on scale {
PropertyAnimation {
duration: 75
}
}
}
}
}
This expands the image you hover over, but I cannot seem to also effect the neighbors. Any advice is appreciated!
I'd suggest a little more robust solution, where you have control over the zoom factor and the spread and decay of influence:
Column {
Slider {
id: foff
from: 1
to: 5
stepSize: 1
value: 2
snapMode: Slider.SnapAlways
}
Slider {
id: sf
from: 0.5
to: 2.5
stepSize: 0.5
value: 0.5
snapMode: Slider.SnapAlways
}
Slider {
id: dmp
from: 1
to: 5
stepSize: 1
value: 1
snapMode: Slider.SnapAlways
}
}
Row {
id: row
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
property int falloff: foff.value // how many adjacent elements are affected
property int current: -1
property real scaleFactor: sf.value // that's how much extra it scales
property real damp: dmp.value // decay of influence
Repeater {
id: iconRepeater
model: 10
Rectangle {
width: 50 * pseudoScale
height: width
anchors.bottom: parent.bottom
color: "red"
border.color: "black"
property real pseudoScale: {
if (row.current == -1) return 1
else {
var diff = Math.abs(index - row.current)
diff = Math.max(0, row.falloff - diff)
var damp = row.falloff - Math.max(1, diff)
var sc = row.scaleFactor
if (damp) sc /= damp * row.damp
diff = diff / row.falloff * sc + 1
return diff
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: row.current = containsMouse ? index : -1
}
Behavior on pseudoScale {
PropertyAnimation {
duration: 150
}
}
}
}
}
It could be something along the lines of this:
Row {
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Repeater {
id: rep
model: ['red', 'yellow', 'pink', 'green', 'teal', 'orchid', 'blue', 'orange']
property int currentIndex: -10
delegate: Rectangle {
anchors.bottom: parent.bottom
// Calculate the width depending on the currently hovered element
width: (rep.currentIndex === index ? 100 : ((rep.currentIndex - index) === 1 || (rep.currentIndex - index) === -1 ? 80 : 50))
height: width
radius: width / 2
color: modelData
MouseArea {
anchors.fill: parent
hoverEnabled: true
// onEntered/Exited did not react. This will work.
onContainsMouseChanged: {
if (containsMouse) rep.currentIndex = index
else rep.currentIndex = -10 // -10 is safe
}
}
// Makes the movement smooth
Behavior on width {
NumberAnimation {}
}
}
}
}
I tried to put in the necessary explainations as comment in the code.
The only thing that would need some tweeking is, that the dots will be initially shifted when the first resizing happens. Putting it on a flickable and some manual labour for the right position handeling could deal with that. Basically you need to shift the flickable by half the width change (in my case ~ 55 or so) to the left, when the mouse enters, and to the right when it leaves again.
You could also do so with a ListView, most likely, but due to the ever changing estimated size of the background, it might be more challanging to get the positioning right.

Particles with different images. [qml]

I am using particle systems. My particles come out from the center but I want different images to come out.
The file path of these images are stored in an array. I made a function to go through that arrangement and return what is in each position.
What returns is the path of each image to be sent to the source of the ImageParticle.
The problem is that if you scroll through the entire array but only return the path of the last image and logically I get a single image instead of the twenty that are stored in the array.
Someone could help me?
Help and suggestions are well accepted.
Here is my code:
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle {
id: bg
width: 1920 //360
height: 1080 //360
color: "black"
ParticleSystem {
id: particleSys
}
Emitter{
id: particles
anchors.centerIn: parent
height: 1; width: 1
system: particleSys
emitRate: 30
lifeSpan: 4000
lifeSpanVariation: 500
maximumEmitted: 1000
size: 5
endSize: 200
velocity: TargetDirection{
targetX: 100; targetY: 100
targetVariation: 360
magnitude: 250
}
}
property var picturesList: [
"images/Image1.png", "images/Image2.png", "images/Image3.png", "images/Image4.png", "images/Image5.png", "images/Image6.png", "images/Image7.png", "images/Image8.png", "images/Image9.png", "images/Image10.png",
"images/Image11.png", "images/Image12.png", "images/Image13.png", "images/Image14.png", "images/Image15.png", "images/Image16.png", "images/Image17.png", "images/Image18.png", "images/Image19.png", "images/Image20.png"
]
function getImage(arr){
var flag = "";
for(var i = 0; i < arr.length ; i++){
flag = arr[i];
console.log("Image: " + arr[i] + " flag: " + flag ) //To check if array is traversing.
}
return flag;
}
ImageParticle{
property var link: getImage(picturesList)
source: link
system: particleSys
}
// To check which image is returned.
MouseArea {
anchors.fill: parent
onClicked: {
console.log(getImage(picturesList))
}
}
}
I am the person who asked the previous question and here I bring the answer.
I know very well that there can be several ways that solve the problem, if you know another way I invite you to share it.
Suggestions about the response are welcome.
Here I share the code:
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle {
id: bg
width: 1920
height: 1080
color: "black"
ParticleSystem {
id: particleSys
}
Emitter{
id: particles
anchors.centerIn: parent
height: 1; width: 1
system: particleSys
emitRate: 30
lifeSpan: 4000
lifeSpanVariation: 500
maximumEmitted: 1000
size: 5
endSize: 200
velocity: TargetDirection{ //4
targetX: 100; targetY: 100
targetVariation: 360
magnitude: 250
}
}
property var picturesList: [
"images/Image1.png", "images/Image2.png", "images/Image3.png", "images/Image4.png", "images/Image5.png", "images/Image6.png", "images/Image7.png", "images/Image8.png", "images/Image9.png", "images/Image10.png",
"images/Image11.png", "images/Image12.png", "images/Image13.png", "images/Image14.png", "images/Image15.png", "images/Image16.png", "images/Image17.png", "images/Image18.png", "images/Image19.png", "images/Image20.png"
]
ItemParticle {
id: particle
system: particleSys
delegate: itemDelegate
}
Component {
id: itemDelegate
Rectangle {
id: container
width: 26*Math.ceil(Math.random()*3); height: width
color: 'white'
Image {
anchors.fill: parent
anchors.margins: 3
source: 'images/Image'+Math.ceil(Math.random()*20)+'.png'
}
}
}
}
As you can see, all the images in the folder will be output.
I simply change the "ImageParticle" to "ItemParticle" and I add a "Component" as a delegate where the desired image will be.
So we would not use the array (omit it in the previous code) since now only the path of the image is concatenated and a random number (to select the image).
I hope you serve them, and if you have a better implementation, please let them know.

QML - Can't get mouse released event when I don't accept mouse pressed event

I would like to dispatch my onPressed event to subsequent objects in my QML tree, (I have indicate mouse.accepted = false and propagateComposedEvents: true ) but I want to keep the onreleased event working on the top level element ..
Here the code :
Item {
width: 200
height: 200
Rectangle {
z: 1
anchors.fill: parent
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onPressed: {
console.log("R1 pressed")
mouse.accepted = false
}
onReleased: {
console.log("R1 released")
mouse.accepted = false
}
}
}
Rectangle {
z: 0
anchors.fill: parent
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onPressed: {
console.log("R2 pressed")
}
onReleased: {
console.log("R2 released")
}
}
}
}
What I expect to see :
qml: R1 pressed
qml: R2 pressed
qml: R1 released
qml: R2 released
What I get :
qml: R1 pressed
qml: R2 pressed
qml: R2 released
How can I solve that ?
Thanks in advance for your help .
Using propagateComposedEvents will not work because its behavior is only defined for composed events, which means one of clicked, doubleClicked and pressAndHold. So when you set mouse.accepted = false in R1's onPressed handler, this has nothing to do with propagateComposedEvents.
Setting mouse.accepted = false instead does what it is supposed to do according to the documentation:
no further events will be sent to this MouseArea until the button is
next pressed.
In my opinion, one should only react to mouse inputs on the topmost MouseArea. As long as R1 and R2 completely overlap with each other, you are making R2 something that it simply isn't. In case R1 is smaller than R2 and you want to do something for R2 in R1's handlers, just move the R2 code to a function and call it from both MouseAreas' event handlers. Or just use bindings to both Mouseareas' pressed state.

Resources