In my test application I am drawing triangles. In the down left corner of this triangle (lest call it nr 2) I need to add text that is just under the line. If the triangle rotates, the text should rotate as well. Heres an "explanation" image:
And here is the code:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window
{
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Item
{
anchors.fill: parent
rotation: 0
property real trheight: 0;
property real hfBase: 0;
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
parent.trheight = (Math.sqrt(Math.pow(trbase, 2) -
Math.pow(trbase / 2, 2))) * hFactor;
parent.hfBase = trbase * hFactor;
ctx.lineTo(parent.hfBase / -2, parent.trheight); // left arm
ctx.lineTo(parent.hfBase / 2, parent.trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}
Text
{
//x: ?
//y: ?
font.family: "RobotoCondensed-Regular";
font.pointSize: 12;
text: qsTr("TEST");
opacity: 0.6;
}
}
}
So ideally the end effect would look like this (edited in paint):
Then on 30 degree rotation like this:
The easiest way would be to have the Text as a child item of the item that is rotated. Then you could just position it at the bottom of it and it would rotate with its parent.
However, your canvas covers the whole window, so your only option (given the current code) is to draw the text when you draw the shape. You can draw text with text(), fillText() or strokeText().
I would suggest rewriting your code to use the first approach I mentioned. Give each shape its own canvas, and make the text a child of the Canvas item (or its parent if you don't want the drop shadow effect to be applied to the text). Rotating an item is much cheaper than rotating by repainting a Canvas.
Related
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 this MWE code here which is drawing a triangle with a set color:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
}
}
I am trying to figure out how to apply gradient to this code, so having 2 colors (for example yellow and red) the triangle would gradient from the first to the other like this (edited in paint):
I tried using Gradient object from the documentation here: https://doc.qt.io/qt-5/qml-qtquick-gradient.html but with no success.
createLinearGradient method is used to draw gradient on canvas.
Just create a gradient using this method, add colors of gradient and finally assign it to fillStyle.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
// create a gradient
var gradient = ctx.createLinearGradient(100,0,100,200)
gradient.addColorStop(0, "yellow")
gradient.addColorStop(1, "red")
ctx.fillStyle = gradient // assign gradient
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
}
}
Here is the output
Also check simple example on gradient from QML book.
I am working on an QML program in which I change the color and the border color of a polygon painted on a Canvas. I have 2 buttons that change the colors, ie. 1 button making the border color red and the other one blue.
My problem is that each time I change the color set, the border of the polygon seems "corrupted", such as the used border colors mixed with each other. The drawn polygon is being resized each time I resize the window. So when I resize, its being repainted I believe. The colors are getting fixed at that point.
My question is: is there a way to disable the overlapping or is there a way to manually force the redrawing of all Canvases in the project?
Ucolors.qml:
import QtQuick 2.9
/**
* #brief Holds the color parameters of the whole UI
*
*/
Item
{
property var canvas
property var text
property var spiderBckgrnd
property var spiderLines
}
main.qml
Ucolors
{
id: colDay
canvas: "#eaedf1"
text: "#0e0e12"
spiderBckgrnd: "#f7fbff"
spiderLines: "#C8CBD0"
}
Ucolors
{
id: colNight
canvas: "#22212c"
text: "#ffffff"
spiderBckgrnd: "#0e0e12"
spiderLines: "#3C3C3F"
}
property var colGlob: colDay
Button
{
id: btn1
anchors.left: parent.left
text: "button1"
onClicked:
{
colGlob = colNight;
}
}
Button
{
id: btn2
anchors.left: btn1.right
text: "button2"
onClicked:
{
colGlob = colDay;
}
}
Then in the code colors are set like this: some_property: colGlob.spiderLines
If you do not explicitely call clearRect() on the context object of your Canvas, any drawing is painted on the existing content.
Since you're drawing a polygon, some antialiasing pixels are added on the edges to get smooth lines. Those pixels are semi-transparent, so when you draw the same polygon over the existing one with another color, color blending occurs on the edges; hence the "corrupted" appearance.
When you change the height or width of the canvas, the context is implicitely cleared, so the "corrupted" edge disappears.
An easy fix is to call clearRect in the onPaint handler of your Canvas.
Canvas
{
id: canvas
onPaint: {
var ctx = getContext("2d")
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw anything you want
...
}
}
Here is a small example that reproduces your problem, and show how it is fixed by calling clearRect
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
ApplicationWindow {
id: root
visible: true
width: 400
height: 200
property string color: "red"
onColorChanged: {
canvas.requestPaint()
}
ColumnLayout
{
anchors.fill: parent
anchors.margins: 20
spacing: 20
RowLayout
{
Layout.alignment: Qt.AlignHCenter
Repeater
{
model: ["red", "blue", "yellow"]
Button
{
text: modelData
highlighted: root.color == modelData
onClicked: {
root.color = modelData
}
}
}
CheckBox
{
id: clearBeforeRepaintCb
text: "Clear before paint"
}
}
Canvas
{
id: canvas
Layout.fillHeight: true
Layout.fillWidth: true
onPaint: {
var ctx = getContext("2d")
if(clearBeforeRepaintCb.checked)
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = root.color
ctx.lineWidth = 10
ctx.beginPath()
ctx.moveTo(10, height/2)
ctx.lineTo(width-10, height/2+3)
ctx.stroke()
}
}
}
}
I am trying to create a rotatable triangle that will always cast a shadow on its base. So far I was able to create the code for making and rotating the triangle but the shadow part is problematic. Here is my minimal code example:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
DropShadow
{
anchors.fill: parent
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: parent
}
}
}
}
I am facing the following problems:
The parser gives me some errors about ShaderEffectSource, that recursive property has to be set, but I dont know whos property is that.
Starting M:\bitbucket\qtworkspace\build-mwe-Desktop_Qt_5_11_0_MinGW_32bit-Debug\debug\mwe.exe...
QML debugging is enabled. Only use this in a safe environment.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
QObject::disconnect: No such signal QObject::screenChanged(QScreen*) in items\qquickscreen.cpp:476
M:/bitbucket/qtworkspace/build-mwe-Desktop_Qt_5_11_0_MinGW_32bit-Debug/debug/mwe.exe exited with code 0
Somewhere in the web I have found a bug report that DropShadow property source cannot accept parent but since Canvas cannot be assigned with id, I am not sure how to do it. Other than this, the shadow renders correctly for rotAngle at 0 degrees:
It doesn't rotate correctly after adding some angle, for example 45 degrees:
It seems to me that the shadow is not being rotated together with the polygon. Can this be adjusted? The last thing is: How to hide shadow behind the polygon? At the moment its interfering with it.
I am not sure either the parser error is connected or not, that's why I have added it together here.
since Canvas cannot be assigned with id
Why Canvas can't be assigned with id?
The following code won't have that warning.
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}
}
About the warning:
I believe that should not consider as a BUG. DropShadow is an Item, so if you nest with its parent, it will do like render a shadow on the shadow.
Why didn't the shadow rotated?
Because you are rotating the contents of Canvas.
Even rotate the polygon, these properties won't change. Always drop the shadow vertically.
horizontalOffset: 0
verticalOffset: 3
Rotate both:
Item {
anchors.fill: parent
rotation: 30
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}
I am a QML / Javascript noob, and would like some help with that.
I want to insert some points (represented as small black circles) onto a white QML canvas element and then run an algorithm on them (such as finding convex hulls via an external geometric library)
Here is my QML code.
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
width: 1000
height: 1000
onPaint: {
var context = getContext("2d");
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
// Record mouse-position
arrpoints = arrpoints.concat([mouseX, mouseY])
console.log(arrpoints)
}
}
}
}
So far the above code, opens up a window, with a QML canvas on it, and can keep track of the positions on the canvas (via the array arrpoints) where I single-clicked with my mouse, and outputs the array of clicked-points to the console.
But now, everytime the arrpoints changes, how do I 'tell' QML to draw a small black circle at that point immediately?
I would have thought the onPaint part of QML would trigger the rendering of the new state immediately, but it seems that part is only for the initial drawing on the canvas, before the user starts interacting with it.
You have to call the canvas requestPaint() function to force the painting. It is also advisable to save the data of the positions appropriately: {"x": x_value, "y": y_value}
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: canvas
width: 1000
height: 1000
onPaint: {
var context = getContext("2d")
context.strokeStyle = Qt.rgba(0, 0, 0, 1)
context.lineWidth = 1
for(var i=0; i < mymouse.arrpoints.length; i++){
var point = mymouse.arrpoints[i]
context.ellipse(point["x"]-5, point["y"]-5, 10, 10)
}
context.stroke()
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
arrpoints.push({"x": mouseX, "y": mouseY})
canvas.requestPaint()
}
}
}
}