Hey posting this question to learn how to curve a line onclick i.e using path-curve i want to curve a line but not hard-coded it occur on mouse click event like if clicked on (400,320) then line at that position Y position will change (let say +75) as in image it is hard-coded but i want curve onclick wherever mouse click event occur it should curve
Here is my implementation. It's not exactly the shape you want. It seems that you can not get something like your image with PathCurve but surely you have much more control over the outcome with PathCubic.
Canvas {
id: canvas
width: 640; height: 640
contextType: "2d"
Path {
id: myPath
startX: 320; startY: 0
PathCurve { id: curvePoint_top; x: 320; y: curvePoint.y - 80 }
PathCurve { id: curvePoint; x: 320; y: 320 }
PathCurve { id: curvePoint_bottom; x: 320; y: curvePoint.y + 80 }
PathCurve { x: 320; y: 640 }
}
onPaint: {
context.clearRect(0,0,width,height);
context.strokeStyle = Qt.rgba(.4,.6,.8);
context.path = myPath;
context.stroke();
}
MouseArea{
anchors.fill: parent
onClicked: {
curvePoint.x = mouseX;
curvePoint.y = mouseY;
canvas.requestPaint();
}
}
}
Related
I have a QML OSM map and a MapQuickItem with Text source item:
MapQuickItem {
property alias rulerRotationAngle: rulerRotation.angle
id: rulerTextMapItem
visible: false
width: 2
height: 2
transform: Rotation {
id: rulerRotation
origin.x: rulerText.width/2;
origin.y: rulerText.height/2;
angle: 0
}
anchorPoint.x: rulerText.width/2
anchorPoint.y: rulerText.height/2
z:5
sourceItem: Text {
id: rulerText; horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Material.color(Material.Amber, Material.Shade100)
text: "0.0 km";
}
}
I also have two points (QtPositioning.coordinate) and I want the text to rotate depending on the angle of the straight line (MapPolyLine) drawn between those points:
function drawRuler()
{
rulerLine.path = [];
rulerLine.addCoordinate(r_firstpoint);
rulerLine.addCoordinate(r_secondpoint);
rulerTextMapItem.visible = true;
rulerTextMapItem.coordinate = QtPositioning.coordinate((r_firstpoint.latitude+r_secondpoint.latitude)/2, (r_firstpoint.longitude+r_secondpoint.longitude)/2);
var atan = Math.atan2(r_secondpoint.longitude-r_firstpoint.longitude, r_secondpoint.latitude-r_firstpoint.latitude);
var angle = ((atan*180)/Math.PI); //used by another MapItem
var textAngle = angle+270;
if(textAngle>90 & textAngle<270) { textAngle+=180 }
if(angle>90 & angle<270) { angle +=180 }
rulerTextMapItem.rulerRotationAngle = textAngle;
}
However, text rotates correctly only at angles that are multiples of 90 degrees. At an angle of 45 degrees, the text deviates from the mappolyline by about 10-20 degrees.
I have no clue why it happens and appreciate any help.
Tried to move transform.origin of MapQuickItem - angle difference only gets bigger.
Tried to use Math.Atan instead of Math.Atan2 - no difference.
The main issue is this line and the order of inputs:
var atan = Math.atan2(
r_secondpoint.longitude-r_firstpoint.longitude,
r_secondpoint.latitude-r_firstpoint.latitude);
latitude should come before longitude, i.e.
var atan = Math.atan2(
r_secondpoint.latitude-r_firstpoint.latitude,
r_secondpoint.longitude-r_firstpoint.longitude);
Generally speaking, to use Math.atan2() to convert to an angle, you need to use one of the following patterns:
let radians = Math.atan2(vectorY, vectorX)
let degrees = Math.atan2(vectorY, vectorX) * 180 / Math.PI
Also over large angles, you definitely should use project your angular coordinates to a flat projection, e.g. QtPositioning.coordToMercator. (This point was raised in one of the earlier comments).
For very small angles you can get away with it because the earth can be approximated to a flat earth directly from angular coordinates, but, as the area goes, this fact quickly disappears.
The following code demonstrates Math.atan2() and how it must work with (vectorY, vectorX) inputs. It has two draggable squares and you watch that the text will always follow the direction of the blue line no matter where the squares are:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
Page {
id: page
width: 200; height: 200
property int startX: rect1.x + rect1.width / 2
property int startY: rect1.y + rect1.height / 2
property int finishX: rect2.x + rect2.width / 2
property int finishY: rect2.y + rect2.height / 2
Rectangle {
id: rect1
x: 40; y: 40
width: 40; height: 40
color: "red"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
Rectangle {
id: rect2
x: 400; y: 250
width: 40; height: 40
color: "red"
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea2
anchors.fill: parent
drag.target: parent
}
}
Shape {
id: shape
ShapePath {
strokeWidth: 4
strokeColor: "blue"
startX: page.startX
startY: page.startY
PathLine {
x: page.finishX
y: page.finishY
}
}
}
Item {
x: (startX + finishX) / 2
y: (startY + finishY) / 2
rotation: Math.atan2(finishY - startY, finishX - startX) * 180 / Math.PI
Frame {
anchors.centerIn: parent
background: Rectangle {
border.color: "black"
}
Text {
text: "Hello World"
}
}
}
}
You can Try it Online!
I'm trying to make an item that can be resized by its edges.
For showing a minimal testcase of the problem it is enough to have its left edge draggable, so here it is:
Rectangle {
id: root
border.width: 1
border.color: 'black'
color: 'red'
// save original position and size at drag start
property real origX: 0
property real origWidth: 0
// drag this item:
Item {
id: dragDummy
x: 0
onXChanged: {
root.x = root.origX + x
root.width = root.origWidth - x
}
}
MouseArea {
anchors.fill: root
drag.target: dragDummy
drag.axis: Drag.XAxis
drag.onActiveChanged: {
// onDragStarted -> Cannot assign to non-existent property "onDragStarted" ???
if(!active) return
root.origX = root.x
root.origWidth = root.width
}
}
}
the problem seems to be that if drag causes parent position to change, that triggers another drag event, causing this flicker:
I'm guessing MouseArea can't help here? Then low level mouse events should be used like in "old-school" apps (i.e. capturing events at root Item, manually compute offset with respect to initial mouse down position, etc...)?
(or I have to move the MouseArea to an ancestor that won't move during drag, which is almost the same...)
There is a nice QML Item type called DragHandler which people often overlook, but I find that it works very well.
This solution is a little more idiomatic than other suggestions in that it uses a declarative style rather than imperative:
import QtQuick 2.15
Item {
id: root
width: 500
height: 100
Item {
height: 100
width: handle.x + handle.width / 2
}
Rectangle {
x: handle.x + handle.width / 2
width: root.width - (handle.x - handle.width/2)
height: 100
border{
width: 1
color: 'black'
}
color: 'red'
}
Item {
id: handle
x: -width / 2
width: 50
height: 100
DragHandler {
yAxis.enabled: false
xAxis{
minimum: -handle.width
maximum: root.width
}
}
}
}
The solution I come up with consists of having two MouseAreas:
a MouseArea moves with the item to drag, that is used only for hit-testing, so its onPressed handler is something like this:
onPressed: (mouse) => {
mouse.accepted = false
root.container.myDragTarget = root
}
onReleased: root.container.myDragTarget = null
another MouseArea, stacked below the others and not moving, handles the mouse position change and the dragging:
onPressed: _start = Qt.point(mouseX, mouseY)
onPositionChanged: {
if(myDragTarget) {
var delta = Qt.point(mouseX - _start.x, mouseY - _start.y)
// do any rounding/snapping of delta here...
_start.x += delta.x
_start.y += delta.y
myDragTarget.x += delta.x
myDragTarget.y += delta.y
}
}
This is able to drag the item reliably.
This is also what I wanted to avoid, because it reinvents mouse drag, but in absence of a better solution it is what I am going to use.
I won't accept this answer as I'm curious to see other ways to approach this problem.
You can workaround the movement and new positioning of the dragged Item by mapping the coordinates with the mapToItem functions.
In my solution, I've not used the drag functionality of the MouseArea as it needs a drag.target. I've used the pressed and position changed signals to implement drag behavior. The only downside is the background Item which is needed for the mapToItem function as it doesn't accept the Window due to it not being an Item.
import QtQuick
import QtQuick.Window
import QtQuick.Shapes
Window {
id: root
visible: true
width: 400
height: 400
Item {
id: background
anchors.fill: parent
Rectangle {
id: rectangle
property int rightX
x: 50
y: 50
width: 200
height: 80
border.width: 1
border.color: "black"
color: "red"
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 10
color: mouseArea.containsMouse || mouseArea.pressed ? "#ff808080" : "#aa808080"
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onPressed: rectangle.rightX = rectangle.x + rectangle.width
onPositionChanged: function(mouse) {
if (mouseArea.pressed) {
var tmp = mouseArea.mapToItem(background, mouse.x, 0)
if (tmp.x <= rectangle.rightX)
rectangle.x = tmp.x
else
rectangle.x = rectangle.rightX
rectangle.width = rectangle.rightX - rectangle.x
}
}
}
}
}
}
}
I'm building a Qt 5.11 application which embeds an openstreetmap QML component.
I just wrote a minimal reproduce case. It consists in displaying objects (five blue dots here) on the map. When hovering the object, a small popup is displayed with some text.
When objects are close to the edge, the popup is not displayed correctly.
I though I would use visibleArea check this, but the property was added in Qt 5.12.
I can't find a solution for the popup to be fully visible. Is there a workaround in Qt 5.11 that I can do?
Here the QML file. Just type qmlscene sample.qml and hover blue dots to view it.
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; name: "The point on the center"; }
ListElement { latitude: 48.235111272600186; longitude: 6.9007217756551995; name: "The point on the right"; }
ListElement { latitude: 48.23512783507458; longitude: 6.896574932520792; name: "The point on the left"; }
ListElement { latitude: 48.23614708436043; longitude: 6.898623901851295; name: "The point on the top"; }
ListElement { latitude: 48.23417574713512; longitude: 6.898641104398024; name: "The point on the bottom"; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 10
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
property alias textVisible: myPopup.visible
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
MouseArea {
x: 0; y: 0;
width: myRect.radius * 2
height: myRect.radius * 2
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onEntered: { myCanvas.textVisible = true }
onExited: { myCanvas.textVisible = false }
}
}
Popup {
id: myPopup
x: myRect.width / 2 - width / 2
y: myRect.height / 2 + 20
visible: false
Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
}
}
}
}
}
}
Any help is greatly appreciated.
You can check if (popup width+popup x) goes outside screen width then change x,y to adjust popup position. You can look into following modified code for Popup component. adjust X and Y as per your marker position.
Popup {
id: myPopup
x: {
if((mapItem.x+myPopup.width) >= root.width)
return -(myRect.width/2)-(width)
else if((mapItem.x-myPopup.width) < 0)
return (myRect.width)
else
return myRect.width / 2 - width / 2
}
y: {
if((mapItem.y+myPopup.height) >= root.height)
return -(myRect.height/2)-(height)
else if((mapItem.y-myPopup.height) < 0)
return (height)
else
return myRect.height / 2 - height / 2
}
visible: false
Label { text: model.name;anchors.centerIn: parent;horizontalAlignment: Text.AlignHCenter; }
}
After looking for a while, I finally came across these two functions: mapToItem and mapFromItem.
So, I first need to map the current item point to the map item coordinate system. Then, I must check the point is inside the map viewport.
If not, I have to adjust the coordinate, and after that, I map the point back to the current item coordinate system. Popup width and height seem to decrease when approaching bottom and right borders, so I had to use contentHeight, contentWidth and padding properties to get the real popup size.
And I had to initialize the popup x and y to a value different of zero to allow mouse event to pass on the blue dot.
Here is the working code, for those who may need it.
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; name: "The point on the center"; }
ListElement { latitude: 48.235111272600186; longitude: 6.9007217756551995; name: "The point on the right"; }
ListElement { latitude: 48.23512783507458; longitude: 6.896574932520792; name: "The point on the left"; }
ListElement { latitude: 48.23614708436043; longitude: 6.898623901851295; name: "The point on the top"; }
ListElement { latitude: 48.23417574713512; longitude: 6.898641104398024; name: "The point on the bottom"; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 10
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
property alias textVisible: myPopup.visible
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
MouseArea {
x: 0; y: 0;
width: myRect.radius * 2
height: myRect.radius * 2
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onPositionChanged: {
myCanvas.textVisible = true;
// absolute position in map coordinate system
var absPos = mapToItem(map, mouse.x, mouse.y);
// margin between mouse pointer and the popup
var cursorMargin = 10;
// extra margin for right and bottom side
var bottomRightSideExtraMargin = 10;
// add the cursor margin to the position
var absPopupX = absPos.x + cursorMargin;
var absPopupY = absPos.y + cursorMargin;
// adjust if the popup is out of view on the bottom or right sides
if (absPos.x + myPopup.contentWidth + myPopup.leftPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.width) {
absPopupX = root.width - (myPopup.contentWidth + myPopup.leftPadding + cursorMargin + bottomRightSideExtraMargin);
}
if (absPos.y + myPopup.contentHeight + myPopup.topPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.height) {
absPopupY = root.height - (myPopup.contentHeight + myPopup.topPadding + cursorMargin + bottomRightSideExtraMargin);
}
// convert back to the current item coordinate system
var popupPos = mapFromItem(map, absPopupX, absPopupY);
myPopup.x = popupPos.x;
myPopup.y = popupPos.y;
}
onExited: {
myCanvas.textVisible = false;
}
}
}
Popup {
id: myPopup
// original offset to allow mouse hover
x: 20; y: 20;
visible: false
Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
}
}
}
}
}
}
I'm trying to make a QML widget that is interactive and will update its model.
Directly modifying the model does not work, so I create a updateModel(int index, real x, real y) signal in InteractiveGraph.
However the change done in onUpdateModel produces no effect on the UI.
I also tried with an onUpdateModel handler such as this: { var newModel = model.slice(); newModel[index].x = x; newModel[index].y = y; model = newModel } but the result is even worse as it breaks the self-dragging of the vertex item, and the model is not updated in the UI anyway.
Is the Array class not a good model for this use-case?
Is there some readymade model that is more appropriate?
InteractiveGraph.qml:
import QtQuick 2.0
/* An interactive undirected graph, whose vertices are draggable */
Item {
id: root
property alias model: repeater.model
property real size: 20
signal updateModel(int index, real x, real y)
Canvas { // render lines connecting the vertices
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.beginPath()
for(var i = 0; i < model.length; i++) {
if(i === 0) ctx.moveTo(model[i].x, model[i].y);
else ctx.lineTo(model[i].x, model[i].y);
}
ctx.closePath()
ctx.stroke()
}
}
Repeater { // instantiate items corresponding to vertices
id: repeater
delegate: Rectangle {
id: self
color: "red"
border.color: "#000"
width: size
height: size
x: modelData.x - size/2
y: modelData.y - size/2
Text { anchors.centerIn: parent; text: index }
MouseArea { // make items self-draggable
anchors.fill: parent
drag.target: self
}
// invert the x and y relation, and send back the change to model
onXChanged: root.updateModel(index, x + size/2, y + size/2)
onYChanged: root.updateModel(index, x + size/2, y + size/2)
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: [
{x: 15, y: 20},
{x: 100, y: 220},
{x: 145, y: 230},
{x: 225, y: 130},
{x: 140, y: 88},
{x: 290, y: 60},
]
onUpdateModel: { model[index].x = x; model[index].y = y; model = model; }
}
}
I should have read Models and Views in Qt Quick, to find out the appropriate model to use is ListModel.
The only caveat is that we can't use x/y property names inside the ListElement child of ListModel, as when using ListElement, the variable modelData is not used anymore, but directly the property name is exposed, and x/y would collide with delegate's properties with the same name.
(In case of name clash, we can use model.fieldName instead of fieldName.)
InteractiveGraph.qml:
Item {
id: root
property alias model: repeater.model
property real size: 20
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = "#eee"; ctx.fillRect(0, 0, width, height)
ctx.beginPath()
for(var i = 0; i < model.count; i++) {
var item = model.get(i)
if(i === 0) ctx.moveTo(item.xPos, item.yPos)
else ctx.lineTo(item.xPos, item.yPos)
}
ctx.stroke()
}
}
Repeater {
id: repeater
delegate: Rectangle {
id: self
color: "red"; border.color: "#000"
x: xPos - size/2; y: yPos - size/2; width: size; height: size
onXChanged: canvas.requestPaint(); onYChanged: canvas.requestPaint()
Text { anchors.centerIn: parent; text: index }
MouseArea {
anchors.fill: parent
drag.target: self
onPositionChanged: model.set(index, {xPos: self.x + size/2, yPos: self.y + size/2})
}
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: graphModel
ListElement {xPos: 15; yPos: 20}
ListElement {xPos: 100; yPos: 220}
ListElement {xPos: 145; yPos: 230}
ListElement {xPos: 225; yPos: 130}
ListElement {xPos: 140; yPos: 88}
ListElement {xPos: 290; yPos: 60}
}
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: graphModel
}
}
I am trying to make a window where I can draw triangles and delete any of them with a Shape{}. In my example below, I can draw 2 types of triangle:
Up triangle: green and filled
Down triangle: yellow and not filled
Basically, I choose the type of triangle (with a button on the right-bottom corner) then I click anywhere on the window to get a triangle.
Once I click a triangle is created dynamically and it is stored in the property triangleList. Then I call the function shape.update() to update data property of shape. This part works well.
Here the function update I use in the Shape (since data is a list, I have to reassign to a new list.):
function update()
{
data = [];
var d = [];
for (var i = 0; i < canvas.triangleList.length; i++)
{
d.push( canvas.triangleList[i] );
}
data = d;
}
My problem appears when I try to delete a triangle. In my example, I can delete the first, the last or all triangles. When I delete a triangle, first I delete the value in triangleList then I call again shape.update(). It works when I delete all triangles or the last one.
However, when I try to delete the first triangle, data doesn't update its objects even if I give it a new list. In fact, it always deletes the last triangle. Below an example:
data property understands there is one less triangle but it doesn't update the other triangles. The only solution I found is to change a property then come back to the original value. This way, it forces the data to update.
But I have to do that for every property that can be different (colors and positions). Hence, my update() function looks like that:
for (var i = 0; i < canvas.triangleList.length; i++)
{
d.push( canvas.triangleList[i] );
////// Change properties one by one to force the refresh
// Force path redraw. Otherwise only the last path can be deleted
d[i].startX++;d[i].startX--;
// Force line color update
d[i].strokeColor = "red"
d[i].strokeColor = d[i].isUp ? "green" : "yellow";
// Force fill color update
d[i].fillColor = "red";
d[i].fillColor = d[i].isUp ? "green" : "transparent";
data = d;
}
I invite you to comment in/out these lines to see the difference.
I could use this trick to force the update but my real code is really bigger than this example and I use bindings.
So my question is: Is there a way to force the update without having to change each property?
Here the full code if you want to test it:
import QtQuick 2.9;
import QtQuick.Controls 2.2;
import QtQuick.Shapes 1.0;
ApplicationWindow {
visible: true; width: 640; height: 480;
Rectangle {
id: canvas;
anchors.fill: parent;
color: "black";
property var triangleList: [];
property bool triangleUp: true;
MouseArea {
anchors.fill: parent;
onClicked: {
var triangle = componentTriangle.createObject(componentTriangle, {
"isUp" : canvas.triangleUp,
"startX" : mouse.x,
"startY" : mouse.y,
}, canvas);
canvas.triangleList.push(triangle);
shape.update();
}
} // MouseArea
Shape {
id: shape;
anchors.fill: parent;
function update()
{
data = [];
var d = [];
for (var i = 0; i < canvas.triangleList.length; i++)
{
d.push( canvas.triangleList[i] );
///////////// HOW TO AVOID THE PART BELOW? /////////////
////// Change properties one by one to force the refresh
// Force path redraw. Otherwise only the last path can be deleted
d[i].startX++;d[i].startX--;
// Force line color update
d[i].strokeColor = "red"
d[i].strokeColor = d[i].isUp ? "green" : "yellow";
// Force fill color update
d[i].fillColor = "red";
d[i].fillColor = d[i].isUp ? "green" : "transparent";
//////////////////////////////////////////////////////
}
data = d;
// I make sure data has at least one path to ensure the refresh
if (data.length == 0)
data.push(Qt.createQmlObject('import QtQuick 2.9; import QtQuick.Shapes 1.0; ShapePath {startX:0;startY:0;}', canvas,
"force_refresh"));
}
} // Shape
} // Rectangle
//////////// Buttons to handle the triangles
Column {
anchors.bottom: parent.bottom;
anchors.right: parent.right;
Button {
text: canvas.triangleUp? "Draw triangleUp" : "Draw triangleDown";
onClicked: { canvas.triangleUp = !canvas.triangleUp; }
} // Button
Button {
text: "Clear first";
onClicked: {
canvas.triangleList[0].destroy();
canvas.triangleList.splice(0,1);
shape.update();
}
} // Button
Button {
text: "Clear last";
onClicked: {
canvas.triangleList[canvas.triangleList.length -1].destroy();
canvas.triangleList.splice(canvas.triangleList.length -1,1);
shape.update();
}
} // Button
Button {
text: "Clear all";
onClicked: {
for (var i = 0; i < canvas.triangleList.length; i++)
canvas.triangleList[i].destroy();
canvas.triangleList = [];
shape.update();
}
} // Button
}
//////////// Component to draw the triangle
Component {
id: componentTriangle;
ShapePath {
property bool isUp;
property real offsetX: isUp? -20 : 20;
property real offsetY: isUp? -30 : 30;
strokeColor: isUp ? "green" : "yellow";
strokeWidth: 3;
fillColor: isUp ? "green" : "transparent";
PathLine { x: startX - offsetX; y: startY - offsetY }
PathLine { x: startX + offsetX; y: startY - offsetY }
PathLine { x: startX; y: startY }
} // ShapePath
}
}
Thank you very much for your help and feel free to ask me if I was not clear.
Have a nice day!
If you are going to handle many items (Shape) it is advisable to use a Repeater with a model. The repeater is responsible for displaying the items based on the information of the model, and to remove the items you just have to remove items from the model.
main.qml
import QtQuick 2.9;
import QtQuick.Controls 2.2;
import QtQuick.Shapes 1.0;
ApplicationWindow {
visible: true; width: 640; height: 480;
QtObject{
id: internals
property bool triangleUp: true;
}
ListModel{
id: datamodel
}
Rectangle {
id: canvas;
anchors.fill: parent;
color: "black";
Repeater{
model: datamodel
Triangle{
x: model.x
y: model.y
isUp: model.isUp
}
}
MouseArea{
anchors.fill: parent
onClicked: datamodel.append({"x": mouse.x, "y": mouse.y, "isUp": internals.triangleUp})
}
}
Column {
anchors.bottom: parent.bottom;
anchors.right: parent.right;
Button {
text: internals.triangleUp ? "Draw triangleUp" : "Draw triangleDown";
onClicked: internals.triangleUp = !internals.triangleUp;
} // Button
Button {
text: "Clear first";
onClicked: if(datamodel.count > 0) datamodel.remove(0)
} // Button
Button {
text: "Clear last";
onClicked: if(datamodel.count > 0) datamodel.remove(datamodel.count - 1)
} // Button
Button {
text: "Clear all";
onClicked: datamodel.clear()
} // Button
}
}
Triangle.qml
import QtQuick 2.9;
import QtQuick.Shapes 1.0
Shape {
id: shape
property bool isUp: false
QtObject{
id: internals
property real offsetX: isUp? -20 : 20;
property real offsetY: isUp? -30 : 30;
}
ShapePath {
strokeWidth: 3;
strokeColor: isUp ? "green" : "yellow";
fillColor: isUp ? "green" : "transparent";
PathLine { x: -internals.offsetX ; y: -internals.offsetY }
PathLine { x: internals.offsetX; y: -internals.offsetY }
PathLine { x: 0; y: 0 }
}
}