How to create a shape with a hole in QML? - qt

I want to create a kind of masking layer which consists of a black area with holes in it. Through the holes it should be possible to see the background. In the simplest version I it's just an rectangle covering the whole screen with an opening in the middle. As shown in the sample picture below.
My first approach was to use QML's Context2D feature: https://doc.qt.io/qt-5/qml-qtquick-context2d.html. Maybe it's totally wrong to do it this way, but maybe it's a good starting point. I tried to create an rectangle (which works) and a clipping area (which doesn't work). Besides the fact my implementation of the clipping doesn't work I would have the problem that the clip() command erases the area outside its path but not inside (at least that's what I understood from the docs: https://doc.qt.io/qt-5/qml-qtquick-context2d.html#clip-method).
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.fillStyle = "black"
ctx.beginPath();
ctx.fillRect(0, 0, Sizes.rootWindow.width, Sizes.rootWindow.height);
ctx.fill();
}

if the hole is exactly centered and the borders are all have the same size, you could use a transparent rectangle with borders:
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: "black"
border.width: 50
}

I found the solution myself by rereading the Qt docs. There is a function to create transparent rectangles inside a given canvas which is called "clearRect(...)": https://doc.qt.io/qt-5/qml-qtquick-context2d.html#clearRect-method
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.fillStyle = "black"
ctx.beginPath();
ctx.fillRect(0, 0, 1000, 600);
ctx.fill();
ctx.clearRect(10, 10, 600, 100)
}
For more advanced shapes OpacityMask suggested by Frank Osterfeld is definitely the way to solve this. But for rectangle I think my solution is more straight forward.

Related

QT-QML: How to create image (line) and work on it

I need to create and move an image on Qt but i cannot understand how to do that.
The image is just a line like the following:
The result that I'd like to obtain is to have a line which I can shift right or left (it should continue even outside the canvas for some cm) and color the line(or glowing it white with another background) when I call a function.
To do that im using a qml file in qt and I'm creating this line by using the Canvas object, but it is far away from the image showed before:
Canvas {
id: mycanvas
width: 1000; height: 600
contextType: "2d"
property var l_mez: 273
property var pp: 245
Path {
id: myPath
startX: 100; startY: 300
PathCurve { x: (100+(mycanvas.l_mez/2)); y: (300+mycanvas.pp/2) }
PathCurve { x: 100+(mycanvas.l_mez*3/2)-40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*3/2)+40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*2); y: 300}
PathCurve { x: 100+(mycanvas.l_mez*5/2); y: 300+mycanvas.pp/2 }
PathCurve { x: 100+(mycanvas.l_mez*7/2); y: 300-mycanvas.pp/2 }
}
onPaint: {
var ctx = getContext("2d");
ctx.strokeStyle = "grey";
ctx.path = myPath;
ctx.lineWidth =9
ctx.stroke();
}
The result is :
The question is, how can i properly draw the line on the first image?
I was thinking I could just create the image and import it but then I could not shift it and change the color of the line as I prefer.
Thanks for the help
PathCurve is really overspecifying the curve in your example which is where the waviness is coming from. I would instead use alternating PathLine and PathArc to achieve that look.
Note, you could do this with an image also if you were willing to give up on changing the line color. You can load your sample image above twice and place them next to each other so that they seamless connect. You would place them both inside of one Item and turn on clipping.
Then you translate them both left or right to give the sense of movement. When you get to the far end of the images in either direction you just jump the translation back by the image width and keep going.
It will look seamless and smooth to the user. Especially if you use an XAnimator as those run in a separate thread.

Weird canvas behavior in QT/QML

I have a strange issue in QML when trying to draw relatively simple graphics.
I'm using PyQt 5 with QT 5.11 and Ubuntu 18.04.
Since the graphics part I'm writing is mostly stationnary (changes about each 1 secs), I found out canvas would be a convenient way to draw graphics without using QPainter. But it's been noting but a nightmare with fonts.
For example, when drawing this simple QML component:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.0
Rectangle {
id: cont_rect
Button {
text: "Repaint"
onClicked: {
cv.clearcv()
cv.requestPaint()
}
}
property string cvfont: "13px Ubuntu"
height: 80
width: 300
color: "#132931"
Canvas {
anchors.fill: parent
antialiasing: true
contextType: '2d'
id: cv
width: cont_rect.width
height: cont_rect.height
function clearcv() {
var ctx = cv.context
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "black"
}
function drawStateLine() {
var ctx = (cv.context == null) ? getContext("2d", {alpha: false}) : cv.context
var x = width/2, y = height - 10
var txt_angle = -45 * 2*Math.PI / 360
ctx.save()
ctx.translate(x,y)
ctx.rotate(txt_angle)
ctx.fillStyle = "royalblue"
ctx.textAlign = "left"
ctx.font = cvfont
ctx.textBaseline = "middle"
ctx.globalAlpha = 1
ctx.fillText("Detected", 0, 0)
ctx.restore()
}
onPaint: {
drawStateLine();
}
}
}
I end up with extremely weird graphics behavior.
The FIRST time my component is rendered, I have good looking text:
Then, when I click my specially crafted button (for the sake of isolating the problem down to a simple component), I have:
Basically, whenever I am trying to repaint my damn canvas, first clearing it (I also tried ctx.reset(), ctx.fillRect with my background color, etc.), I end up with font rendering that is much less readable and that bothers me.
Is there anyone here who has an idea on how to avoid this?
I don't know if it's useful, but I use a 27in display with 1080p resolution.
Thanks!

Top round transparent window

I'm currently learning QML and I want to create a top round transparent window.
I've build something that looks like that, but it seems wrong for multiple reason.
Here's my code:
Window {
id: app
visible: true
width: 70
height: 70
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
Rectangle {
anchors.fill: parent
radius: parent.width / 2.0
color: "black"
MouseArea {
property point clickPos: "1,1"
anchors.fill: parent
drag.target: parent
onPressed: {
clickPos = Qt.point(mouse.x,mouse.y)
}
onPositionChanged: {
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
app.x += delta.x;
app.y += delta.y;
}
onDoubleClicked: app.close()
}
}
}
using these flags in the main :
QQuickWindow::setDefaultAlphaBuffer(true);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
The main problem is that the background is not transparent.
I think it is because the 'round' rectangle is fully painted !?
I've tried multiple flags (Qt.Tool, Qt.Transparent, ...) but none works.
I was wondering if I started well to do what I want (I think I don't) and what is the best way to do it.
I've seen the clipping property for the qml item but I also see there's performance issues. I don't know if it's a good idea to use that property.
I'm running on Qt 5.10 and Win7 using MSVC as compiler.
Thank you
EDIT: Adding transparent background color to the window
Adding an answer, just so I can post an image to prove to you that all you need is to set the color:
Window {
id: app
visible: true
width: 70
height: 70
flags: Qt.Window | Qt.FramelessWindowHint
color: "#00000000"
Rectangle {
anchors.fill: parent
radius: parent.width / 2.0
color: ma.pressed ? "red" : "black"
MouseArea {
id: ma
property point clickPos: "1,1"
anchors.fill: parent
drag.target: parent
onPressed: {
clickPos = Qt.point(mouse.x,mouse.y)
}
onPositionChanged: {
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
app.x += delta.x;
app.y += delta.y;
}
onDoubleClicked: app.close()
}
}
}
And the result:
I didn't use any of the flags you are setting from C++, maybe setDefaultAlphaBuffer() is breaking it for you.
I've figured it out.
Searching more deeply on the net and thx to #dtech, I found this article
It was the exact same problem as me. But without #dtech, I would never have thought about the graphic card problem, which led me to this solution.
It seems that you need to have the aero mode enable on windows in order to be able to use transparency on Qt.
I activated the aero mode and then retried the given solution (the one of #dtech), it works very nice.
EDIT: It's a well known "bug" on Qt
Now that I have the solution, it seems obvious but I didn't think about it before.
Thx everyone

Change polygon size on window resize

In my QML application I am drawing a triangle manually using "Canvas" object. The problem is I cannot figure out how to change the drawn object size each time I resize the main window.
Preferably, it would be convenient If I could simply redraw the triangle each time a window resize occurs. But I don't know how could this be done in QML. In bare QT, I guess I would subscribe to window size changed signal. What is the proper way of doing this in QML?
Edit: The program is described here: Change polygon color dynamically
In my main window there is a rectangle called rectMain. It is always the same size as the window. Then inside that rectangle there is another one, called rectTemp. In that rectangle I draw the canvas.
Edit 2: So far I have figure out how to react manually on window size changes:
property int lastWindowWidth: 0
property int lastWindowHeight: 0
function windowSizeChanged()
{
if ((lastWindowWidth == width) && (lastWindowHeight == height))
return;
console.log("New height: ", height, " New width: ", width);
lastWindowWidth = width
lastWindowHeight = height
}
onHeightChanged: windowSizeChanged();
onWidthChanged: windowSizeChanged();

Image border in QML

I want to have an animated border over an Image. But to my surprise, only Rectangle is able to provide border. I want to have a dotted line moving round the Image. How to get such animation. This is my sample code which just provide a border to the Image.
Rectangle {
width: image.width + 5
height: image.height + 5
border.color: "yellow"
border.width: 5
color: "transparent"
Image {
id: image
anchor.centerIn: parent
source: ""
}
}
Because Canvas element in QML does not have the setLineDash() method as JavaScript canvas has (but you can still emulate it, see here), the easiest way (imho) is to use BorderImage with a custom image with dotted pattern. Please see example how to use BorderImage here.
Also you can write your own QML element derived from QQuickPaintedItem or QQuickItem in C++.
Take BorderImage instead of Rectangle:
Image {
BorderImage {
}
}

Resources