QML MouseArea: how to propagate mouse events to other mouse areas? - qt

I have a complex dialog that contains lots of controls in complex layouts. I need to add a cursor marker: vertical line which is drawn above this dialog and should follow the mouse cursor.
I do not understand how to implement this.
Simplified sample code:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 640
height: 480
Button {
anchors.centerIn: parent
width: 100
height: 50
text: "Button"
highlighted: hovered
}
Rectangle {
id: cursorMarker
width: 1
color: "black"
anchors.top: parent.top
anchors.bottom: parent.bottom
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
cursorMarker.x = mouse.x
}
}
}
In this sample, MouseArea placed above the button and intercepts all mouse messages. So the button is not highlighted when the mouse cursor moved above it. In case when MouseArea placed below the button then the cursor marker is not positioned correctly when mouse moved over the button.
But I need both: the cursor marker is positioned correctly above the whole dialog and the button working correctly.
How to solve this problem?

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 640
height: 480
property int mousePosX : 0
property int mousePosY : 0
Button {
anchors.centerIn: parent
width: 100
height: 50
text: "Button"
// Test if the mouse is within the Button
highlighted: mousePosX > x && mousePosX > y && mousePosX < x + width && mousePosY < y + height
}
Rectangle {
id: cursorMarker
x: mousePosX
width: 1
color: "black"
anchors.top: parent.top
anchors.bottom: parent.bottom
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
mousePosX = mouse.x
mousePosY = mouse.y
}
}
}
I suggest the following (Not exactly what you are asking, but another approach to implement the same) :
Store the mouse pos in some vars.
Then check if current mouse pos is within the button objects (by seeing if it lies within the bounding box of that button)

Related

Looking for alternate function to QML ListView's itemAtIndex due to version conflict

I'm using Qt 5.12, so I can't access ListView's itemAtIndex which was introduced in Qt 5.13.
I can't upgrade Qt due to my project/platform related restrictions. Is there a way to find the item at a given index for ListView with the Qt versions prior to 5.13?
Otherwise, is there a way to get mouse positions of an item based on index?
I'm having a listview with adjacent items having different width(alternate items have same width). I'm trying to access listview's item which is of less width compared to the adjacent item. The space between two items in the above picture is also an item which is marked as dummy. I'm able to get the index of each item (both actual & dummy), but the x position I get seems to be incorrect as the rectangle cursor is not getting placed in the intended item's position.
Please suggest alternatives that gives the similar functionality as itemAtIndex. Thanks.
In the following example, I declare a MouseArea in each delegate. So, once the mouse hovers over that delegate, we trigger MouseArea.onEntered and can know which item, because that delegate will have the corresponding index value:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
background: Rectangle { color: "#848895" }
ColumnLayout {
anchors.fill: parent
ListView {
id: listView
Layout.fillWidth: true
Layout.preferredHeight: 150
model: 20
orientation: ListView.Horizontal
delegate: MyDelegate { }
ScrollBar.horizontal: ScrollBar {
height: 20
policy: ScrollBar.AlwaysOn
}
highlight: Item {
z: 2
Rectangle {
width: 10
height: parent.height
color: "lightsteelblue"
border.color: "black"
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Frame {
anchors.centerIn: parent
background: Rectangle { }
Text {
text: qsTr("ListView.currentIndex = %1").arg(listView.currentIndex)
}
}
}
}
}
// MyDelegate.qml
import QtQuick
import QtQuick.Controls
Rectangle {
property ListView listView: ListView.view
width: 120
height: listView.height - 20
implicitWidth: width
implicitHeight: height
color: "transparent"
Rectangle {
border.color: "grey"
color: "white"
y: 20
height: parent.height - y * 2
width: parent.width
Text {
anchors.centerIn: parent
text: qsTr("Item %1").arg(modelData + 1)
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: listView.currentIndex = index
}
}
You can Try it Online!

QML: Button inside MouseArea does not pass mouse hover events

There is a button inside MouseArea:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
id: background
anchors.fill: parent
color: mouseArea.containsMouse ? "red" : "green"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: console.log("onEntered")
onExited: console.log("onExited")
//onPositionChanged: console.log("position", mouse.x, mouse.y)
Button {
id: button
anchors.centerIn: parent
width: 200
height: 100
}
}
}
In Qt5, if you hover the mouse over the button, the containsMouse property will remain true and the onExited and onEntered handlers will not be called.
If you switch to Qt6, then when you move the mouse over the button, the onExited and onEntered handlers are called and containsMouse becomes false.
I need hover events behavior in Qt6 the same as in Qt5. Is it possible?

Reproducing OS Minimize Behaviour in a Custom Titlle Bar done in QML

I'm being tasked with creating a customized title bar for our application. It needs to have rounded corners and a settings button, amongst other things. It will run exclusively on windows.
Our application uses Qt and QML for the front end.
So the only way I could find how to do this is by making the application window frameless and creating the title bar from scratch.
This is my test code:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0
ApplicationWindow {
id: mainWindow
visible: true
visibility: Window.Maximized
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground
//flags: Qt.Window | Qt.WA_TranslucentBackground
color: "#00000000"
TitleBar {
id: mainTitleBar
width: mainWindow.width;
height: mainWindow.height*0.018
color: "#aaaaaa"
onCloseApplication: {
Qt.quit();
}
onMinimizeApplication: {
mainWindow.visibility = Window.Minimized
}
}
Component.onCompleted: {
console.log("Size: " + mainWindow.width + "x" + mainWindow.height)
mainTitleBar.width = mainWindow.width
mainTitleBar.height = mainWindow.height*0.023;
}
Rectangle {
id: content
width: mainWindow.width
height: mainWindow.height - mainTitleBar.height
anchors.top: mainTitleBar.bottom
anchors.left: mainTitleBar.left
color: "#00ff00"
}
}
And
Here is the title bar code (TitleBar.js file):
import QtQuick 2.0
import QtQuick.Controls 2.0
Rectangle {
/*
* Requires setting up of
* -> width
* -> height
* -> title text
* -> icon path.
* -> Background color.
*/
id: vmWindowTitleBar
border.width: 0
x: 0
y: 0
radius: 20
signal closeApplication();
signal minimizeApplication();
// The purpose of this rectangle is to erase the bottom rounded corners
Rectangle {
width: parent.width
height: parent.height/2;
anchors.bottom: parent.bottom
anchors.left: parent.left
border.width: 0
color: parent.color
}
Text {
id: titleBarText
text: "This is The Title Bar"
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: parent.width*0.018
}
Button {
id: minimizeButton
width: height
height: vmWindowTitleBar.height*0.8
anchors.right: closeButton.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: parent.width*0.018
background: Rectangle {
id: btnMinimizeRect
color: vmWindowTitleBar.color
anchors.fill: parent
}
onPressed:{
minimizeApplication()
}
scale: pressed? 0.8:1;
contentItem: Canvas {
id: btnMinimizeCanvas
contextType: "2d"
anchors.fill: parent
onPaint: {
var ctx = btnMinimizeCanvas.getContext("2d");
var h = minimizeButton.height;
var w = minimizeButton.width;
ctx.reset();
ctx.strokeStyle = minimizeButton.pressed? "#58595b": "#757575";
ctx.lineWidth = 6;
ctx.lineCap = "round"
ctx.moveTo(0,h);
ctx.lineTo(w,h);
ctx.closePath();
ctx.stroke();
}
}
}
Button {
id: closeButton
//hoverEnabled: false
width: height
height: vmWindowTitleBar.height*0.8
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: parent.width*0.018
background: Rectangle {
id: btnCloseRect
color: vmWindowTitleBar.color
anchors.fill: parent
}
onPressed:{
closeApplication()
}
scale: pressed? 0.8:1;
Behavior on scale{
NumberAnimation {
duration: 10
}
}
contentItem: Canvas {
id: btnCloseCanvas
contextType: "2d"
anchors.fill: parent
onPaint: {
var ctx = btnCloseCanvas.getContext("2d");
var h = closeButton.height;
var w = closeButton.width;
ctx.reset();
ctx.strokeStyle = closeButton.pressed? "#58595b": "#757575";
ctx.lineWidth = 2;
ctx.lineCap = "round"
ctx.moveTo(0,0);
ctx.lineTo(w,h);
ctx.moveTo(w,0);
ctx.lineTo(0,h);
ctx.closePath();
ctx.stroke();
}
}
}
}
Now the problem comes with minimizing the application. The first thing I realize is that when using the Qt.FramelessWindowHint flag, the icon does not appear in the Windows Taskbar. Furthermore if I minimize it this happens:
And If I click on it, it doesn't restore.
So my question is, is there a way to reproduce regular minimize behavior when pressing the minimize button?
Or alternatively, is there a way I can completely customize the title bar of the application so that I can achieve the look and feel set by our UI designer?
NOTE: The current look is just a quick test. I have not set the gradient, font, or the aforementioned settings button.
As for me, playing with frameless windows and transparent background is kind of workaround. As I know, the only way to apply a custom shape to the window is QWindow::setMask. Sinse Window is derived from QWindow you can do that in this way.
For example, in the main.cpp:
QWindow *wnd = qobject_cast<QWindow *>(engine.rootObjects().at(0));
auto f = [wnd]() {
QPainterPath path;
path.addRoundedRect(QRectF(0, 0, wnd->geometry().width(), wnd->geometry().height()), 30, 30);
QRegion region(path.toFillPolygon().toPolygon());
wnd->setMask(region);
};
QObject::connect(wnd, &QWindow::widthChanged, f);
QObject::connect(wnd, &QWindow::heightChanged, f);
f();
Since you 'cut' the shape from the window itself, excluding title bar and frames you can leave the window flags as is.
Look at this way, I try to create something that you do but change completely your code.
the problem that makes change in your window size after you minimize the window is that you didn't set the initial width and height for the window so when you minimize the app it shows in the minimum width and height.
so you need to add just this in main.qml and set the initial width and height to the maximum.
width: maximumWidth
height:maximumHeight
but In the code below I change something else too.
For example, you didn't need to emit signals and then catch them in main.qml
you have access to mainWindow in TitleBar.qml.
in TitleBar.qml :
import QtQuick 2.0
import QtQuick.Controls 2.0
Rectangle {
anchors.fill: parent
height: 30
Row {
id: row
anchors.fill: parent
Label {
id: label
text: qsTr("Title ")
}
Button {
id: button
x: parent.width -80
text: qsTr("close")
onClicked:
{
mainWindow.close()
}
}
Button {
id: button1
x: parent.width -160
width: 90
text: qsTr("Minimized")
onClicked:
{
mainWindow.showMinimized()
}
}
}
}
and in main.qml :
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0
import "."
Window {
id: mainWindow
visible: true
visibility: Window.FullScreen
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground
width: maximumWidth
height:maximumHeight
Rectangle {
id: content
anchors.fill: parent
x: 0
y: 20
width: mainWindow.width
height: mainWindow.height - mainTitleBar.height
anchors.top: mainTitleBar.bottom
anchors.left: mainTitleBar.left
color: "#00ff00"
}
TitleBar {
id: mainTitleBar
color: "#aaaaaa"
anchors.bottomMargin: parent.height -40
anchors.fill: parent
}
}

I faced a conflict in using DropShadow and Ripple

I wrote a Ripple item in a Rectangle Item and enabled clip property of the Rectangle to prevent the Ripple drawing get out of that Rectangle.
without DropShadow:
Everything works fine until I add a DropShadow to that Rectangle. Although it is outside the Rectangle item, but it neutralizes the clip effect in left and right side of the Rectangle.
with DropShadow:
Here is my code:
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12
import QtQuick.Controls.Material.impl 2.12
Item{
width: 400
height: 100
DropShadow {
anchors.fill: itemRect
horizontalOffset: 0
verticalOffset: 0
radius: 12.0
samples: 17
color: "#50000000"
source: itemRect
}
Rectangle{
id:itemRect
anchors.fill: parent;
anchors.margins: 8;
border.width: 1
radius: 5;
clip: true;
MouseArea{
id: button
anchors.fill: parent
onPressed: {
ripple.x=mouseX-(ripple.width/2);
ripple.y=mouseY-(ripple.height/2);
}
Ripple {
id: ripple
clipRadius: 2
x:40
width: itemRect.width*2
height: itemRect.height*2
pressed: button.pressed
active: false
color: "#10000000"
// layer.enabled: true
// layer.effect: OpacityMask {
// maskSource: Rectangle
// {
// width: ripple.height
// height: ripple.height
// radius: ripple.height
// }
// }
}
}
}
}
I tested OpacityMask for layer.effect of Ripple Item. But it didn't have any effects.
Finaly i wrote my own Ripple.
It can be used anywhere without any issues.
https://github.com/mmjvox/Another-Ripple
here is my code:
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12
import AnotherRipple 1.0
Item{
width: 400
height: 100
DropShadow {
anchors.fill: itemRect
horizontalOffset: 0
verticalOffset: 0
radius: 12.0
samples: 17
color: "#50000000"
source: itemRect
}
Rectangle{
id:itemRect
anchors.fill: parent;
anchors.margins: 8;
border.width: 1
radius: 5;
clip: true;
SimpleRipple{
id: ripple
anchors.fill: parent;
color: "#50ffa070"
}
MouseArea{
id: button
anchors.fill: parent
onClicked: {
// In this example MouseArea accepts pressed event to provide clicked(released) event.
// Ripple can't sense pressed event, so I called pressed method of Ripple.
ripple.pressed(mouse.x,mouse.y);
// Some Other Operations
}
}
}
}

How to change the position when the parent is resized?

How do I properly change the x, y of an object so that it changes its position when the parent is resized? There is, I will introduce that if I drag the rectangle to the middle, then when the window is resized, it should remain in the middle. (middle for example only, rectangle can be moved freely)
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
onWidthChanged: {
block.x -= block.previousWidth - width
block.previousWidth = width
}
onHeightChanged: {
block.y -= block.previousHeight - height
block.previousHeight = height
}
Rectangle {
id: block
color: "red"
width: 50
height:50
x: 100
y: 50
property int previousWidth: 0
property int previousHeight:0
Component.onCompleted: {
previousWidth = parent.width
previousHeight = parent.height
}
MouseArea {
anchors.fill: parent
drag.target: block
}
}
}
I must admit, at first I was not impressed by the question. However, when I thought about it, it represents a very interesting and valid use case. So I would be happy to provide a solution.
Solution
I would approach the problem like this:
Make the frame a child of the background image.
Instead of manually calculating the coordinates, use Item.scale to scale the image, effectively preserving the relative position of the frame with regard to the image.
Example
Here is an example I have prepared for you to demonstrate how the proposed solution could be implemented:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
Image {
anchors.centerIn: parent
source: "alphabet.png"
scale: parent.width/sourceSize.width
Rectangle {
id: frame
width: parent.width/7
height: parent.height/4
border.color: "black"
color: "transparent"
antialiasing: true
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
}
Result
The example produces the following result:
Original window
Resized window
The frame is moved
The window is resized again
As I said in my comment, the best solution is anchoring, for example:
Window {
id: root
width: 600
height: 400
title: qsTr("Parent window")
visible: true
flags: Qt.WindowStaysOnTopHint
Grid {
anchors.fill: parent
Repeater {
model: 16
Rectangle {
width: root.width / 4
height: root.height / 4
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
}
}
}
Rectangle {
border {
width: 5
color: "black"
}
color: "transparent"
width: root.width / 4
height: root.height / 4
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: root.width / 4
anchors.bottomMargin: root.height / 4
}
}

Resources