So as the title says , I'm having trouble making my GUI stop being laggy, for example when I drag the left side of the GUI it stretch's in a weird way and has slow animation , also my cursor reaches the point before the GUI and it takes like half a second to reach the exact shape that is wanted.
this the case for the four 8 possible resizing options (left,right,bottom right etc ..) .
I'm using Qt Creator and writing in QML .
and I'm using this function to resize :
mainWindow.startSystemResize("some edge");
and the following flags are applied to the window application:
flags: Qt.Window | Qt.FramelessWindowHint
Edit
So I made the following example code and it has the same kind of problem :
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: mainWindow
width: 1000
height: 580
minimumWidth: 800
minimumHeight: 500
visible: true
color: "#00000000"
title: qsTr("Test")
//removing the windows default bar
flags: Qt.Window | Qt.FramelessWindowHint
Rectangle {
id: bg
color: "#2c313c"
anchors.fill: parent
Rectangle {
id: rectangle
x: 205
y: 133
width: 200
height: 200
color: "#e03d3d"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
//for resizing using the mouse
property int bw: 5
MouseArea {
id: resizingWindow
anchors.fill: parent
hoverEnabled: true
cursorShape: {
const p = Qt.point(mouseX, mouseY);
const b = bw + 10; // Increase the corner size slightly
if (p.x < b && p.y < b) return Qt.SizeFDiagCursor;
if (p.x >= width - b && p.y >= height - b) return Qt.SizeFDiagCursor;
if (p.x >= width - b && p.y < b) return Qt.SizeBDiagCursor;
if (p.x < b && p.y >= height - b) return Qt.SizeBDiagCursor;
if (p.x < b || p.x >= width - b) return Qt.SizeHorCursor;
if (p.y < b || p.y >= height - b) return Qt.SizeVerCursor;
}
// acceptedButtons: Qt.NoButton // don't handle actual events
DragHandler {
id: resizeHandler
// grabPermissions: TapHandler.TakeOverForbidden
target: null
onActiveChanged: if (active) {
const p = resizeHandler.centroid.position;
const b = bw + 5; // Increase the corner size slightly
let e = 0;
if (p.x < b) { e |= Qt.LeftEdge }
if (p.x >= width - b) { e |= Qt.RightEdge }
if (p.y < b) { e |= Qt.TopEdge }
if (p.y >= height - b) { e |= Qt.BottomEdge }
mainWindow.startSystemResize(e);
}
}
}
}
PS:
In order to run this you need to put the code in Qt Creator and then run the QML code and try to resize in one the 8 directions
Related
Is it possible to move the rectangle outside the window? The only thing I came up with is to write custom logic that will resize the top window when moving the rectangle outside the window.
Current behavior (imgur .gif):
Current behavior
Desired behavior (imgur .png):
Desired behavior
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: root
width: 300
height: 500
visible: true
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
color: "#00000000"
Rectangle {
id: draggable
color: "blue"
x: 100
y: 100
width: 100
height: 100
MouseArea {
anchors.fill: parent
property real lastMouseX: 0
property real lastMouseY: 0
onPressed: {
lastMouseX = mouseX
lastMouseY = mouseY
}
onMouseXChanged: {
draggable.x += (mouseX - lastMouseX)
}
onMouseYChanged: {
draggable.y += (mouseY - lastMouseY)
}
}
}
Rectangle {
color: "blue"
x: 100
y: 300
width: 100
height: 100
// ...
}
}
Windows can be children of other Windows. The Window behavior is still subject to certain platform-dependent behavior, but on a Desktop environment child Windows should still be able to move outside the parent Window. So simply changing your Rectangle to be a Window will give you the desired effect.
Window {
id: root
Window {
id: draggable
...
}
}
I'm currently creating a virtual dashboard and i would like to get type of progress bar behind needle as in this link: https://forum.qt.io/topic/89307/qml-circular-gauge-styling-needle-trailing-colour-glow .
So far, I've only done needle progress bar using Canvas. I dont understand how to use conicalGradient with opacity mask to achieve
the effect that I need.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
ApplicationWindow
{
id:root
visible: true
width: 1024
height: 600
property int external_width: 534
property int external_height: 533
property bool external_reverse: false
property int external_angle: 0
property int externalstart_angle: 138 //138
property int external_angle_limit: 360//264
property int external_radius: 235
property int external_lineWidth: 60
Canvas {
id: external_progress_bar
width: root.external_width
height: root.external_height
x: (root.width - width)/2
y: (root.height - height)/2
property real angle: 260
property real nextAngle: (Math.PI/180)*angle
property color col: "red"
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.beginPath();
ctx.arc(width/2, height/2, root.external_radius, (Math.PI/180) * root.externalstart_angle,(Math.PI/180) * root.externalstart_angle + nextAngle, root.center_reverse);
ctx.lineWidth = root.external_lineWidth
ctx.strokeStyle = col
ctx.stroke()
}
}
}
I would be very grateful for the sample code with explanations.
What you can do is using a ConicalGradient and a OpacityMask.
Create the same canvas with a different color. Then, a ConicalGradient from transparent to white and a mask to reduce the painted area to your canvas:
Canvas {
id: external_progress_bar
...
visible: false // Not visible (it will be painted by the mask)
}
ConicalGradient {
id: progress
anchors.fill: external_progress_bar
angle: 45.0 // Change this angle to move the gradient effect
gradient: Gradient {
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 0.1; color: "white" } // Just a part of the canvas
}
visible: false // Not visible (it will be painted by the mask)
}
OpacityMask {
anchors.fill: progress
source: external_progress_bar
maskSource: progress
invert: true
}
You will get :
For more explanation on OpacityMask, see the Qt documentation
I have some sibling Rectangle elements with a radius property so they appear as circles. Each has a child Item that has a child MouseArea, the purpose of the Item being to implement a "round mouse area" effect (original SO answer). The Item and MouseArea are instrumented such that clicks and drags will only take effect within the visible circular shape of the Rectangle, not within the bounding box that is the real footprint of the Rectangle.
Unfortunately there is a glitch illustrated below. The desired outcome when dragging at the dot is for circle 1 to move, and this happens in most circumstances. However, it does not happen when you create create circle 1 then circle 2 then move your mouse cursor to the dot. If you do that and attempt to drag or click, your interaction will fall through to the background full-window MouseArea and create a new circle.
The cause of this problem is that when the mouse cursor moves to the dot from circle #2, the mouseX and mouseY for circle #1's MouseArea do not get updated. When circle #2 allows the click to propagate downward, it hits the Rectangle of circle #1 but then circle #1's Item claims containsMouse is false and it propagates downward again.
As soon as the mouse cursor leaves the footprint of circle #2's bounding rectangle, such as by moving a bit up or left from the dot, circle #1's MouseArea gets updated and its containsMouse becomes true and it starts capturing clicks and drags again.
I have tried a handful of potential solutions and not gotten much farther than the code below.
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
visible: true
width: 640
height: 480
property real spotlightRadius: 100
MouseArea {
visible: true
anchors.fill: parent
onClicked: {
spotlightComponent.createObject(parent, {
"x": x + mouseX - spotlightRadius,
"y": y + mouseY - spotlightRadius,
"width": spotlightRadius * 2,
"height": spotlightRadius * 2
})
}
}
Component {
id: spotlightComponent
Rectangle {
id: spotlightCircle
visible: true
x: parent.x
y: parent.y
width: parent.width
height: parent.height
radius: Math.max(parent.width, parent.height) / 2
color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
Item {
anchors.fill: parent
drag.target: parent
onDoubleclicked: parent.destroy()
onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }
property alias drag: mouseArea.drag
//FIXME when moving the mouse out of a higher element's containsMouse circle
// but still inside its mouseArea.containsMouse square, lower elements'
// mouseArea do not update, so their containsMouse doesn't update, so clicks
// fall through when they should not.
property bool containsMouse: {
var x1 = width / 2;
var y1 = height / 2;
var x2 = mouseArea.mouseX;
var y2 = mouseArea.mouseY;
var deltax = x1 - x2;
var deltay = y1 - y2;
var distance2 = deltax * deltax + deltay * deltay;
var radius2 = Math.pow(Math.min(width, height) / 2, 2);
return distance2 < radius2;
}
signal clicked(var mouse)
signal doubleclicked(var mouse)
signal wheel(var wheel)
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
//FIXME without acceptedButtons, propagated un-accepted clicks end up with the wrong coordinates
acceptedButtons: parent.containsMouse ? Qt.LeftButton : Qt.NoButton
propagateComposedEvents: true
onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
onDoubleClicked: { if (parent.containsMouse) { parent.doubleclicked(mouse) } }
onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
drag.filterChildren: true
}
}
}
}
}
This is not the exact solution for your problem, but this is how I overcame the root of the issue.
In my application there is a MouseArea that overlaps a large chunk of the scene which is a QQuickFrameBufferObject. This is where I draw the 3D scene. Since you cannot propagate a QHoverEvent in QML, you will have to catch the position changed signal using the onPositionChanged handler and invoke a method in C++ which will send a QHoverEvent to the required items.
QML:
MouseArea {
onPositionChanged: {
model.sendHoverEvent(Qt.point(mouse.x, mouse.y))
}
}
C++:
class TreeViewModel : public QAbstractListModel
{
// ...
void TreeViewModel::sendHoverEvent(QPointF p) {
QHoverEvent hoverEvent(QEvent::HoverMove, p, p);
QApplication::sendEvent(mApplication.graphicsLayer(), &hoverEvent);
}
};
I think that using HoverHandler instead of MouseArea can give the desired result in this case because they stack as you'd expect i.e. they are full "transparent" for mouse movement, nor are they blocked by any MouseArea that happens to be on top.
You need to reject the pressed event of your underlying MouseArea. It should be enough to solve your problems. If the pressed event is rejected, the click will automatically be forwarded to the underlying sibling items. propagateComposedEvents and filterChildren are useless in your case.
Note that if the wheel event causes the z coordinate of your spotlightCircle to become less than 0, it will no longer accept mouse event since they will be caught by the "Creation" MouseArea
import QtQuick 2.10
import QtQuick.Controls 2.3
ApplicationWindow {
visible: true
width: 640
height: 480
property real spotlightRadius: 100
MouseArea {
visible: true
anchors.fill: parent
onClicked: {
spotlightComponent.createObject(parent, {
"x": x + mouseX - spotlightRadius,
"y": y + mouseY - spotlightRadius,
"width": spotlightRadius * 2,
"height": spotlightRadius * 2
})
}
}
Component {
id: spotlightComponent
Rectangle {
id: spotlightCircle
visible: true
x: parent.x
y: parent.y
width: parent.width
height: parent.height
radius: Math.max(parent.width, parent.height) / 2
color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
Item {
anchors.fill: parent
onDoubleClicked: parent.destroy()
onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }
signal clicked(var mouse)
signal pressed(var mouse)
signal doubleClicked(var mouse)
signal wheel(var wheel)
property alias drag: mouseArea.drag
property bool containsMouse: {
var x1 = width / 2;
var y1 = height / 2;
var x2 = mouseArea.mouseX;
var y2 = mouseArea.mouseY;
var deltax = x1 - x2;
var deltay = y1 - y2;
var distance2 = deltax * deltax + deltay * deltay;
var radius2 = Math.pow(Math.min(width, height) / 2, 2);
return distance2 < radius2;
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
drag.target: spotlightCircle
onPressed: { if (parent.containsMouse) { parent.pressed(mouse) } else { mouse.accepted = false } }
onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
onDoubleClicked: { if (containsMouse2) { parent.doubleClicked(mouse) } }
onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
}
}
}
}
}
Consider the code below, which allows you to insert successive points represented as black circles onto the canvas. The aim of the code, is to
join each successive point by a red line segment.
However, the code gives me a canvas which looks like this
Only the first and second points have been joined by a red segment. None of the other points have been joined by segments. How do I fix that?
Here is the QML code
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 500
height: 500
function clear() {
var ctx = getContext("2d");
ctx.reset();
mycanvas.requestPaint();
}
Path {
id: myPath
startX: 0; startY: 100
PathLine { x: 400; y: 500 }
}
property var arrpoints : []
onPaint: {
var context = getContext("2d");
// Render all the points as small black-circles
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
for(var i=0; i < arrpoints.length; i++){
var point= arrpoints[i]
context.ellipse(point["x"], point["y"], 10, 10)
}
context.fill()
context.stroke()
// Join successive points with red segments
for (var j=1 ; j < arrpoints.length ; j++){
var start = arrpoints[j-1]
var end = arrpoints[j]
context.beginPath();
context.lineWidth = 2;
context.moveTo(start["x"], start["y"]);
context.strokeStyle = "red"
context.lineTo(end["x"], end["y"]);
}
context.stroke();
}
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
console.log( mycanvas.arrpoints )
}
}
}
Button {
text: "Clear Points"
anchors.top : mycanvas.bottom
onClicked: {
mycanvas.arrpoints.length = 0
mycanvas.clear()
console.log( mycanvas.arrpoints )
}
}
}//Window
According to the documentation:
object ellipse(real x, real y, real w, real h)
Creates an ellipse within the bounding rectangle defined by its
top-left corner at (x, y), width w and height h, and adds it to the
path as a closed subpath.
The ellipse is composed of a clockwise curve, starting and finishing
at zero degrees (the 3 o'clock position).
That is, an ellipse is drawn inscribed in a rectangle determined by the position (x, y) and the size w, h, so we must obtain the topLeft point using the mouse point and the radius.
For lines it is not necessary to make a path for each line, you just have to use a path, and then create the line and move to a next point as shown below:
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 500
height: 500
function clear() {
var ctx = getContext("2d");
ctx.reset();
mycanvas.requestPaint();
}
Path {
id: myPath
startX: 0; startY: 100
PathLine { x: 400; y: 500 }
}
property real radius: 10
property var arrpoints : []
onPaint: {
var context = getContext("2d");
context.save()
if(arrpoints.length > 0){
for(var i=0; i < arrpoints.length; i++){
var point= arrpoints[i]
context.ellipse(point["x"]-radius, point["y"]-radius, 2*radius, 2*radius)
}
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
context.fill()
context.stroke()
context.beginPath()
var start = arrpoints[0]
context.moveTo(start["x"], start["y"])
for(var j=1; j < arrpoints.length; j++){
var end= arrpoints[j]
context.lineTo(end["x"], end["y"])
context.moveTo(end["x"], end["y"])
}
context.closePath()
context.strokeStyle = "red"
context.lineWidth = 2;
context.stroke()
}
context.restore()
}
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
}
}
}
Button {
text: "Clear Points"
anchors.top : mycanvas.bottom
onClicked: {
mycanvas.clear()
console.log( mycanvas.arrpoints )
}
}
}//Window
I have a GridView in QML ApplicationWindow which should be filled
with some Items.
I place my items with JS function "placeItems".
But the problem is that when Component.onCreated signal of ApplicationWindow is called the GridView is not yet layouted.
For example, the GridView has x coordinate equal to -425 in Component.onCreated of ApplicationWindow.
If I call the same function a second later - everything is ok and GridView
has correct coordinates (=75).
I've check the Qt reference back and forth and haven't found other signals (something like onLayouted or onLayoutComplete) that may be helpful.
The question is when to call "placeItems" so the GridView in ApplicationWindow
already has correct coordinates?
UPDATE1:
To observe the bad behaviour just click File->Start after the application started. It will place the item in the correct place.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
function max (a,b) { return a>b ? a : b; }
function min (a,b) { return a<b ? a : b; }
property int sizeMin: width < height ? width : height
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
property var items: []
function centerCell(column,row) {
return {x: field.x + (column + 0.5) * cellWidth,
y: field.y + (row + 0.5) * cellHeight}
}
function placeItem(name, col, row) {
var c = centerCell(col,row)
items[name].centerX = c.x
items[name].centerY = c.y
}
function placeItems() {
placeItem ("a", 3, 3)
//placeItem ("b", 4, 4)
}
}
Component.onCompleted: field.placeItems()
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int centerX: 0
property int centerY: 0
property int margin: 5
property var cell: field.items["a"] = this
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
x: centerX - width/2
y: centerY - height/2
color: "red"
opacity: 0.5
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Start")
onTriggered: field.placeItems();
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
function placeItem(name, col, row) {
items[name].anchors.horizontalCenter = field.left;
items[name].anchors.verticalCenter = field.top;
items[name].anchors.horizontalCenterOffset = (col + 0.5) * cellWidth;
items[name].anchors.verticalCenterOffset = (row + 0.5) * cellHeight;
}
The key is to anchor the element in the grid view and then move it according to your calculations.
BTW, you know that QML has built in functions Math.min/Math.max?
EDIT
Or better yet, why not define the bindings in rectItemA directly?
Another, less hackish way to have the right behavior (don't play with Timer with layout, really, it's a bad idea):
You are defining your Rectangle as an item centered in a instance of a item belonging to your GridView. So, I use a little of your way (getting an item at the r row and the c column in the gridview), and then I reparent the Rectangle to this item. To make it centered, it is only needed to anchor it to the center of its newly bound parent.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
property int sizeMin: Math.min(width, height)
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
function cellAt(row, col) {
return itemAt(row * (dimCell + dimCellSpacing), col * (dimCell + dimCellSpacing));
}
}
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int margin: 5
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
anchors.centerIn: parent
color: "red"
opacity: 0.5
}
Component.onCompleted: {
rectItemA.parent = field.cellAt(3, 3);
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
Why don't you just delay the placeItems function so it runs with a tiny delay so that when it runs the "static" components are all completed.
Timer {
interval: 250 // might want to tune that
repeat: false
onTriggered: placeItems()
}
In a perfect world, Component.onCompleted would nest perfectly and the root item would be the last one to be emitted. But unfortunately Qt does not guarantee the order, and indeed as I did a quick test, the parent item emits (or at least responds to) onCompleted BEFORE the child items.
And if you don't want to pollute your QML file with the timer, you can actually instantiate a "helper object" from a JS function dynamically, set it to do its work and then delete itself when done. Similar to the "reparent helper" I outlined in this answer: Better way to reparent visual items in QML but rather delete itself on the timer timeout rather than in the JS function which would not give it the time to trigger.