How can I dynamically draw a polygon and make its points/markers movable in QML? - qt

I have came across a problem where I have to dynamically draw a polygon on QML Map using mouse and make its points movable so that user can change those points location. There is a very nice answer to a similar question which helped me to at least add some points/markers dynamically and connect them through lines but it doesn't allow the markers to be movable.
Can somebody please help me in this regard?

In the following code a marker will be added with the right click and you can drag a marker with the right click.
The logic of adding is simple is to detect the right click of the mouse and obtain with that information the position by adding it to the model associated with the MapItemView that handles the markers and the MapPolygon points.
On the other hand, the logic of the drag is first to detect without a marker has been pressed so that a MouseArea attached to each marker is used obtaining the index of that element, disabling the "gesture" of the map. The MouseArea of the markers was configured so that they continue to propagate the mouse events to the other elements since the detection of the release must be done on the map, for this the positionChanged and Released signals are used with which the position of the marker is updated and restore the variables when necessary.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 5.14
import QtPositioning 5.14
Window {
visible: true
width: 640
height: 480
property int currentIndex: -1
ListModel{
id: polygonmodel
}
Map {
id: map
anchors.fill: parent
plugin: Plugin {
name: "osm"
}
gesture.enabled: currentIndex == -1
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MapItemView{
z: polygon.z + 1
model: polygonmodel
delegate: MapQuickItem{
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
coordinate: QtPositioning.coordinate(model.coords.latitude, model.coords.longitude)
sourceItem: Image {
width: 40
height: 40
source: "http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_red.png"
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.LeftButton
propagateComposedEvents: true
onPressed: {
currentIndex = index
mouse.accepted = false
}
}
}
}
}
MapPolygon{
id: polygon
border.color: "green"
border.width: 10
}
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
var point = Qt.point(mouse.x, mouse.y)
var coord = map.toCoordinate(point);
if (mouse.button == Qt.RightButton)
addMarker(coord)
}
onPositionChanged: {
if (currentIndex != -1){
var point = Qt.point(mouse.x, mouse.y)
var coord = map.toCoordinate(point);
if(coord.isValid)
moveMarker(currentIndex, coord)
}
}
onReleased: {
if (mouse.button == Qt.LeftButton && currentIndex != -1){
var point = Qt.point(mouse.x, mouse.y)
var coord = map.toCoordinate(point);
if(coord.isValid)
moveMarker(currentIndex, coord)
currentIndex = -1;
}
}
}
}
function moveMarker(index, coordinate){
polygonmodel.set(index, {"coords": coordinate})
var path = polygon.path;
path[index] = coordinate
polygon.path = path
}
function addMarker(coordinate){
polygonmodel.append({"coords": coordinate})
polygon.addCoordinate(coordinate)
}
}

Related

Qt Select At Most 1 Marker on Map

In my code every marker that I clicked are selected(turn into green from red). I want just 1 can change. When I click another marker the marker I clicked before turns red again. Or When I click an empty area the marker I clicked before turns red again.
In qml my Item's code:
Component {
id: hazardous_img
MapQuickItem {
id: hazardousitem
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: position
property bool isClicked: false
MouseArea {
anchors.fill: parent
onDoubleClicked: {
mainwindow.hazardousIconClicked(mapview.toCoordinate(Qt.point(mouse.x,mouse.y)))
}
onClicked: {
if (isClicked === false) {
image.source = "qrc:/grn-pushpin.png"
isClicked = true
} else {
image.source = "qrc:/red-pushpin.png"
isClicked = false
}
}
}
sourceItem: Image {
id: image
source: "qrc:/red-pushpin.png"
}
}
}
In QML this is usually done with using a ButtonGroup, but as you're not using AbstractButtons you need to write it yourself. Here is my solution for it.
I've used the ListModel to not only store the coordinates of each marker, but also a selected flag which is set to false by default. In the delegate I'm using the selected data role to show if a marker is selected or not.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Map")
ListModel { id: markerModel }
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
var jsonObject = JSON.parse(JSON.stringify(coordinate))
jsonObject["selected"] = false
markerModel.append(jsonObject)
}
onClicked: map.deselectAll()
}
MapItemView {
model: markerModel
delegate: markerDelegate
}
function deselectAll() {
for (var i = 0; i < markerModel.count; ++i)
markerModel.setProperty(i, "selected", false)
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
required property bool selected
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.selected ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
map.deselectAll()
markerModel.setProperty(markerItem.index, "selected", true)
}
}
}
}
}
}
I came up with yet another solution without looping over all items in the model. It just stores the index of the selected marker in a dedicated property. This has the drawback that if the model order changes the index can become invalid, also potential multi selection is hard to handle, but on the other hand it is faster because it doesn't need to iterate over all items.
I experimented a lot with DelegateModel, it seems to be a perfect match if one could use it in combination with MapItemView, because of the groups and the attached properties like inGroupName.
After that I've tried ItemSelectionModel, but it seems it is only intended to be used in combination with a view, e.g. TreeView. I couldn't find out how to generate a QModelIndex in QML without a TreeView.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
property int selectedMarker: -1
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
markerModel.append(JSON.parse(JSON.stringify(coordinate)))
}
onClicked: root.selectedMarker = -1
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.index === root.selectedMarker ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: root.selectedMarker = markerItem.index
}
}
}
}
}
I promise this is the last answer on that question.
This one is using an ItemSelectionModel and a few undocumented functions, e.g. ListModel.index(row, col).
itemSelectionModel.hasSelection is used in the color binding to trigger a reevaluation in order to call isRowSelected and set the color accordingly whenever the selection has changed.
If the user clicks on the background the clear() is called to clear the selection.
I think out of the three this is the best solution. It can be easily upgraded to allow multi selection as shown below. Also the ItemSelectionModel can be used by other views to show the data and selection.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
import QtQml.Models 2.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: function(mouse) {
markerModel.append(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
}
onClicked: itemSelectionModel.clear()
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
ItemSelectionModel {
id: itemSelectionModel
model: markerModel
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: {
itemSelectionModel.hasSelection
return itemSelectionModel.isRowSelected(markerItem.index) ? "red" : "gray"
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: itemSelectionModel.select(markerModel.index(markerItem.index, 0),
ItemSelectionModel./*ClearAnd*/Select)
}
}
}
}
}

How to perform dynamic conversion between mouse coordinates and world coordinates with QtQuick3D?

Description/ Code
I have a Qt Quick 3D View and corresponding scene that was designed to be compiled on Qt 6.3.0
import QtQuick
import QtQml
import QtQuick3D
import QtQuick3D.Helpers
Window {
width: 800
height: 600
visible: true
property var selectedItem
property bool mousePressed: false
function multiply_vectors(vec1, vec2) {
return Qt.vector3d(vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z);
}
View3D {
renderMode: View3D.Inline
camera: camera
anchors.fill: parent
width: 800
height: 600
x: 0
y: 0
id: view
environment: SceneEnvironment {
clearColor: "black"
backgroundMode: SceneEnvironment.Color
depthTestEnabled: false
depthPrePassEnabled: true
}
Model {
id: rootEntity
pickable: true
source: "#Cube"
materials: PrincipledMaterial {
baseColor: "red"
roughness: 0.1
}
position: Qt.vector3d(25.0, 15.0, -60.0)
scale: Qt.vector3d(1.0, 1.0, 1.0)
}
PerspectiveCamera {
id: camera
position.z: 330.0
position.y: 0.75
eulerRotation.x: -12
clipNear: 0.0
clipFar: 1600.0
}
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
id: mouseArea
onPressed: function (mouse) {
var result = view.pick(mouse.x, mouse.y);
if (result.objectHit) {
selectedItem = result.objectHit;
mousePressed = true;
} else {
mousePressed = false;
}
}
onMouseXChanged: function(mouse) {
if (mousePressed) {
var viewCoords = view.mapFromGlobal(mouseArea.mapToGlobal(mouse.x, mouse.y));
var sceneCoords = Qt.vector3d(viewCoords.x, viewCoords.y, 0);
var worldCoords = view.mapTo3DScene(sceneCoords);
worldCoords.z = selectedItem.z
selectedItem.position = multiply_vectors(worldCoords, Qt.vector3d(Math.abs(camera.z - selectedItem.z), Math.abs(camera.z - selectedItem.z), 1.0))
}
}
onReleased: function (mouse) {
mousePressed = false
}
}
Component.onCompleted: {
camera.lookAt(rootEntity)
}
}
}
Overview
The use case is that whenever the mouse is pressed while pointing at the cube, whenever the mouse moves it will cause the cube to move along with it to the corresponding point in the 3d Scene.
This works great when looking from a point that is on the same z-axis. However when looking at the object from a point say along the x-axis, the model will move along the x-axis instead of following the mouse position.
Question
How can I modify the business logic in onMouseXChanged: function(mouse) { to correctly transform the matrix (or equivalent transform) to consistently match the mouse position irregardless of the camera's position relative to the Model?
If I understood you correctly, you need to move the object with the mouse parallel to the camera regardless of the camera position and model scaling? I admit that I don't have a solution, but still it's better than the original code. First of all, do not set the clipNear to 0, it would make the frustum degenerate and break the projection math.
Secondly, I would suppose that the code which sets the object position should look like
selectedItem.position = view.mapTo3DScene(
Qt.vector3d(mouse.x, mouse.y,
view.mapFrom3DScene(selectedItem.position).z))
The docs say that mapFrom3DScene/mapTo3DScene should interpret the z coordinate as the distance from the near clip plane of the frustum to the mapped position. However when I move it towards the sides of the window the object gets larger, whereas it should get smaller.
Here's the complete code with a few corrections of mine:
import QtQuick
import QtQml
import QtQuick3D
import QtQuick3D.Helpers
Window {
width: 800
height: 600
visible: true
property var selectedItem
property bool mousePressed: false
View3D {
renderMode: View3D.Inline
camera: camera
anchors.fill: parent
width: 800
height: 600
x: 0
y: 0
id: view
environment: SceneEnvironment {
clearColor: "black"
backgroundMode: SceneEnvironment.Color
depthTestEnabled: false
depthPrePassEnabled: true
}
Model {
id: rootEntity
pickable: true
source: "#Cube"
materials: PrincipledMaterial {
baseColor: "red"
roughness: 0.1
}
position: Qt.vector3d(25.0, 15.0, -60.0)
scale: Qt.vector3d(2.0, 1.0, 0.5)
}
PerspectiveCamera {
id: camera
position.z: 330.0
position.y: 100
position.x: 700
eulerRotation.x: -12
// Note 1: clipNear shouldn't be 0, otherwise
// it would break the math inside the projection matrix
clipNear: 1.0
clipFar: 1600.0
}
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
id: mouseArea
onPressed: function (mouse) {
var result = view.pick(mouse.x, mouse.y);
if (result.objectHit) {
selectedItem = result.objectHit;
mousePressed = true;
} else {
mousePressed = false;
}
}
onPositionChanged: function(mouse) {
if (mousePressed) {
// Note 2: recalculate the position, since MouseArea has
// the same geometry as View3D we can use coords directly
selectedItem.position = view.mapTo3DScene(
Qt.vector3d(mouse.x, mouse.y,
view.mapFrom3DScene(selectedItem.position).z))
}
}
onReleased: function (mouse) {
mousePressed = false
}
}
Component.onCompleted: {
camera.lookAt(rootEntity)
}
}
}
After spending a while experimenting with different approaches, I found that mapping the mouse coordinates to the 3d space wasn't fully supported by the Qt API in terms of when the mouse is not fixed over an active object.
So, instead, the way that I made a workout was by casting a new RayCast each time the mouse moves and storing the offset when the mouse is pressed originally and then translating the item based on the result of the raycast and lining up the offset by translating by the normalized matrix with a small scalar.
onMouseXChanged: function (mouse) {
if (mousePressed) {
if (selectedItem != null) {
var result = view.pick(mouse.x, mouse.y)
if (result.objectHit) {
if (result.objectHit == selectedItem) {
var mouseGlobalPos = mouseArea.mapToGlobal(
mouse.x, mouse.y)
var mouseViewPos = view.mapFromGlobal(
mouseGlobalPos)
var mouseScenePos = result.scenePosition
var resultPos = result.position
/* here we subtract the result of the new raycast by the starting offset and then normalize
* the result and multiply it by a scalar 3 to determine the amount of offset the Model
* under the mouse is from where the mouse was originally pressed, so we can translate it */
var differencePos = resultPos.minus(
startMousePressSelectedItemLocalDragOffset).normalized(
).times(3)
selectedItem.position = selectedItem.position.plus(
differencePos)

Get mouse x & y in nested MouseArea with hoverEnabled

I made a simplified MapImage component which allows to zoom and pan an image with the mouse. This component uses Flickable and MouseArea components. The MapImage component just handles image display, zooming and panning. I want to use another MouseArea in the MapImage instance in main.qml (to be able to place objects using a Canvas but this is not important here). This is not the job of MapImage, so I really need this second MouseArea component.
I need to set the hoverEnabled property to true because I need onPositionChanged and others events... But this property seems to cause problems with mouseX and mouseY values taken from my updateFlickable function. When I'm zooming with the mouse wheel, zoom does not occur at the mouse position...
I've made a minimal example available here or in a gist.
Any hint to solve this?
main.qml
import QtQml.Models 2.11
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
MapImage {
id: map
height: 600
width: 800
imageSource: "https://images.unsplash.com/photo-1651634099253-720df02a0d50"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true // this is required to use onPositionChanged
preventStealing: false
onPressed: {
// needed for flickable
mouse.accepted = false;
}
onPositionChanged: {
// do something.
}
}
}
MapImage.qml
import QtQuick 2.11
Item {
id: root
property alias imageSource: image.source
Flickable {
id: flickable
anchors.fill: parent
contentWidth: props.originalImageWidth
contentHeight: props.originalImageHeight
Image {
id: image
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if (status === Image.Ready) {
props.originalImageWidth = sourceSize.width;
props.originalImageHeight = sourceSize.height;
props.changeCurrentScale(1);
}
}
// define the image display size
width: flickable.contentWidth;
height: flickable.contentHeight;
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onWheel: {
wheel.accepted = false;
props.changeCurrentScale(wheel.angleDelta.y);
}
}
}
QtObject {
id: props
// original image size
property int originalImageWidth
property int originalImageHeight
property real scaleStep: 0.2
property real currentScale: 0.1
onCurrentScaleChanged: updateFlickable(currentScale);
function updateFlickable(scale) {
console.log(mouseArea.mouseX, mouseArea.mouseY); // <------ I am no longer able to get mouse x and y coordinates
flickable.resizeContent(originalImageWidth * scale, originalImageHeight * scale, Qt.point(mouseArea.mouseX, mouseArea.mouseY));
flickable.returnToBounds();
}
function changeCurrentScale(wheelDelta) {
if (wheelDelta > 0) currentScale = currentScale * (1 + scaleStep);
else currentScale = currentScale / (1 + scaleStep);
}
}
}
}
Finally found a solution. I had to add a new property in my MapImage component. This property role is to store the updated position of the mouse in the parent mouse area in the parent coordinate system. After that, I have to use mapToItem to convert in the flickable.contentItem coordinate system.
main.qml
import QtQml.Models 2.11
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
MapImage {
id: map
height: 600
width: 800
imageSource: "https://images.unsplash.com/photo-1651634099253-720df02a0d50"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true // this is required to use onPositionChanged
preventStealing: false
onPressed: {
// needed for flickable
mouse.accepted = false;
}
onPositionChanged: {
// CHANGE HERE
// the position must be updated on every position change
map.parentMouseAreaPosition = Qt.point(mouse.x, mouse.y);
// TO HERE
}
}
}
MapImage.qml
import QtQuick 2.11
Item {
id: root
property alias imageSource: image.source
// CHANGE HERE
// the current mouse position in the parent mouse area in parent coordinate system
property var parentMouseAreaPosition: Qt.point(0, 0)
// this function maps the parent coordinate system to that of contentItem
function __mapToContentItem(x, y) {
return mapToItem(flickable.contentItem, x, y);
}
// TO HERE
Flickable {
id: flickable
anchors.fill: parent
contentWidth: props.originalImageWidth
contentHeight: props.originalImageHeight
Image {
id: image
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if (status === Image.Ready) {
props.originalImageWidth = sourceSize.width;
props.originalImageHeight = sourceSize.height;
props.changeCurrentScale(1);
}
}
// define the image display size
width: flickable.contentWidth;
height: flickable.contentHeight;
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onWheel: {
wheel.accepted = false;
props.changeCurrentScale(wheel.angleDelta.y);
}
}
}
QtObject {
id: props
// original image size
property int originalImageWidth
property int originalImageHeight
property real scaleStep: 0.2
property real currentScale: 0.1
onCurrentScaleChanged: updateFlickable(currentScale);
function updateFlickable(scale) {
// CHANGE HERE
// get the mapped point
let point = __mapToContentItem(root.parentMouseAreaPosition.x, root.parentMouseAreaPosition.y);
console.log(point.x, point.y);
flickable.resizeContent(originalImageWidth * scale, originalImageHeight * scale, point);
// TO HERE
flickable.returnToBounds();
}
function changeCurrentScale(wheelDelta) {
if (wheelDelta > 0) currentScale = currentScale * (1 + scaleStep);
else currentScale = currentScale / (1 + scaleStep);
}
}
}
}
I don't know if there is a better solution.

QML append new PathCurve elements to List<PathElements> in ShapePath

To be specific: I want to connect several Points on a Map with a spline curve. New points can be added with a Mouseclick and should also be connected to the existing Path. The points are stored within a model, so I can access them also in C++.
Unfortunately I can't figure out, how I can append new PathCurve elements to the existing List in the Shape::ShapePath Object.
I expected that something like this should work:
...
MapQuickItem {
coordinate: QtPositioning.coordinate(0.0000, 0.0000)
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
zoomLevel: 15
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
myPath.pathElements.push( new PathCurve(mouse.x, mouse.y) ) //does not work
}
}
I also tried to fill the PathElements from C++, but the PathCurve class seems to be private and is only usable from within QML. Hardcoding PathCurve Elements works just fine like in every online example, but I want to dynamically modify the list of pathelements.
Any help would be appreciated very much!
You must dynamically components using the function createQmlObject, but for this you must bear in mind that it depends on the zoomLevel that you apply to the MapQuickItem the relation of sizes since that depends on the painting, the PathCurve does not use coordinates of the window but the coordinates of the Shape, and the Shape is painted according to how it is configured. So for in this case the zoomLevel of the MapQuickItem must be 0 or the same as the map. Considering the above, the solution is:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Shapes 1.12
import QtPositioning 5.9
import QtLocation 5.3
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl" "osm" "esri"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 14
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
MapQuickItem {
id: map_item
coordinate: QtPositioning.coordinate(59.91, 10.75)
anchorPoint.x : 0
anchorPoint.y : 0
zoomLevel: map.zoomLevel
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
}
}
MouseArea {
id: mousearea
anchors.fill: map
onClicked: {
var item_pos = map.fromCoordinate(map_item.coordinate, false)
var pathcurve = Qt.createQmlObject('import QtQuick 2.12; PathCurve {}',
myPath);
pathcurve.x = mouse.x - item_pos.x
pathcurve.y = mouse.y - item_pos.y
myPath.pathElements.push(pathcurve)
}
}
}

How to pan a QML item on MouseArea drag

I have a Qt app which runs on iOS and OSX using Qt 5.10 commercial version. I have a QML item which hosts an image. I am trying to pan the QML item when user's finger drags on it OR mouse is dragged.
Following is somewhat I am trying to make my QML item pannable:
Code:
MyQmlItem {
id: my_qml_item
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
onXChanged: {
if (my_qml_item_mouse_area.drag.active) {
console.log("x = " + x)
my_qml_item.x = // what to set x here to move my_qml_item wrt finger or mouse pressed movement
}
}
onYChanged: {
if (my_qml_item_mouse_area.drag.active) {
console.log("y = " + y)
my_qml_item.y = // what to set y here to move my_qml_item wrt finger or mouse pressed movement
}
}
MouseArea {
id: my_qml_item_mouse_area
anchors.fill: parent
drag {
id: drag_area
target: my_qml_item
axis: Drag.XandYAxis
}
}
}
I understand that I have to update the x and y position of MyQmlItem when onXChanged and onYChanged is active and x y are getting updated. But I am struggling to figure how I should re-calculate the new my_qml_item.x and my_qml_item.y
Question:
I am getting x and y updates on onXChanged and onYChanged as well. The basic question is, how to calculate plus continuously update my_qml_item.x and my_qml_item.y.
Are there any good examples of Qt/QML for panning or dragging a QML item?
Is there some way to replicate the following anchors by only setting default x and y? Because, it falls in direct conflict with dragging the QML component
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
Anchors should not be used if you wish to drag as it links some part of the geometry of the items.
In your case, you only need to establish the specific positions at certain times, such as when the application starts, so instead of setting the anchors you could use the properties "x", "y", "width" and "height" .
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
id: app
visible: true
visibility: "FullScreen"
title: qsTr("Scroll")
function resetPosition(){
item.x = Screen.orientation === Qt.PortraitOrientation ? (Screen.width - item.width)/2 : (Screen.height - item.height)/2
item.y = 0
}
Image {
id: item
source: "http://doc.qt.io/qt-5/images/declarative-qtlogo.png"
onStatusChanged: {
if(status == Image.Ready)
resetPosition()
}
MouseArea{
anchors.fill: parent
drag.target: item
drag.axis: Drag.XAndYAxis
onClicked: resetPosition()
}
}
property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation || Screen.primaryOrientation === Qt.InvertedPortraitOrientation
property bool isLandscape: Screen.primaryOrientation === Qt.LandscapeOrientation || Screen.primaryOrientation === Qt.InvertedLandscapeOrientation
onIsPortraitChanged: resetPosition()
onIsLandscapeChanged: resetPosition()
}

Resources