Context
I am trying to create a tiled map using QML without using QtLocation. I am trying to design the application such that only essential tiles are being loaded. Here is the strategy I have come up with.
Each tile is 256x256 px. The tiles are placed in a Grid class with nested Repeaters. The xOffset determines the tiles that should be loaded into the grid.
Grid{
id: mapgrid
rows: 6
columns: 9
spacing: 1
Repeater{
model: 6
Repeater{
model: 9
property int outerIndex: index
Tile{
imgsrc: "image://provider/" + maprect.zoomLevel + '/' + (index + maprect.xOffset) + '/' + (outerIndex+maprect.yOffset)
}
}
}
}
When the grid is shifted by 256 pixels left, a new set of tiles should be loaded in. This can be achieved by changing the offset values. I can then shift the grid by 256 pixels again so its back in the view.
onXChanged: {
if(x <= -512){
maprect.x = -256;
maprect.xOffset++;
}
}
Problem
The movement of my grid is controlled by a MouseArea with drag.target set to it. It appears that the MouseArea controls the coordinates until the mouse is released. Here is a simple example:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Text{
z:1
text: "X: " + rect.x
}
MouseArea{
id:marea
anchors.fill: rect
drag.target: rect
}
Rectangle{
id: rect
height: 256
width: 256
color: "red"
onXChanged: {
if(x > 100){
x = 0;
}
}
}
}
If you move the box right past x > 100, indeed x=0 is set. However that is because the actual value of the box is still 100+. If you move the box left again (x<100) without releasing, you'll find that the box is back at its actual x value. Sorry if it sounds confusing, its easier to understand to see it for yourself.
What I am looking for is an alternative way to load tiles or a fix to the MouseArea problem such that I can actually change the position of an item while its being dragged by the MouseArea.
Also, would it be possible for me to implement a different version of MouseArea so I can deal with this problem at a lower abstraction?
Update
I have been able to achieve the effect I wanted to a certain degree. It appears to be working fine but QML is detecting a binding loop.
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: wind
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
id: wrapper
height: wind.height
width: wind.width
MouseArea{
id:marea
anchors.fill: wrapper
//drag.target: rect
property int initialX: 0
property int iniMouseX;
onEntered: {
initialX = rect.x
iniMouseX = mouseX
console.log ("set")
}
}
Rectangle{
id: rect
height: 256
width: 256
border.color: "green"
x: marea.initialX + (marea.mouseX - marea.iniMouseX)
color: "red"
onXChanged: {
if(rect.x > 200){
marea.initialX -= 200;
}
}
}
}
}
https://imgur.com/a/zmu5eiO
The problem with the updated code is initialX bind with rect.x, so if rect.x be updated it will invoke onXChanged, and if rect.x > 200 will update initialX again, this will become an infinite loop.
So to fix it here is the solution, make the event goes like flow :
Window {
id: wind
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
id: wrapper
height: wind.height
width: wind.width
MouseArea{
id:marea
anchors.fill: wrapper
//drag.target: rect
property int initialX: 0
property int iniMouseX;
onEntered: {
initialX = rect.x
iniMouseX = mouseX
console.log ("set")
}
onMouseXChanged: {
if(rect.x > 200){
marea.initialX -= 200;
}
rect.x = marea.initialX + (marea.mouseX - marea.iniMouseX)
}
}
Rectangle{
id: rect
height: 256
width: 256
border.color: "green"
color: "red"
}
}
}
Related
How can I return resize logic of borders in Frameless Window?
The frame windows has this logic:
Code in QML:
import QtQuick
import QtQuick.Controls 2.5
import Qt5Compat.GraphicalEffects
import NR 1.0
Window {
id: mainWindow
width: 640
height: 720
visible: true
title: qsTr("Hello World")
flags: Qt.Window | Qt.FramelessWindowHint
color: "transparent"
// (1)
MouseArea {
id: bottomArea
height: 5
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
cursorShape: Qt.SizeVerCursor
onPressed: {
previousY = mouseY
}
onMouseYChanged: {
var dy = mouseY - previousY
mainWindow.setHeight(mainWindow.height + dy)
}
}
// Some code of another Items here
}
I tried this code for left side:
MouseArea {
id: leftSideMouseArea
anchors.fill: parent
property point lastMousePos: Qt.point(0, 0)
onPressed: { lastMousePos = Qt.point(mouseX, mouseY); }
onMouseXChanged: mainWindow.width += (mouseX + lastMousePos.x)
}
I put this code in (1) place, but it doesn't work - on click (without move) windows resize to the rigth and app crashes with error:
QQuickPaintedItem::textureProvider: can only be queried on the
rendering thread of an exposed window
This looks like on picture:
Can you help me?
Thanks!
Since Qt5.15, we have startSystemResize, which performs a native resizing and is recommended against using methods like comparing the click position to the current position.
The function is very simple; once you pass an edge, the window begins to resize.
An example of a frameless window is shown below:
CustomWindow.QML
Change the offset from the window's edges where the mouse can be pressed by using this property.
property int edgeOffest: 5
Also for moving the window as well You can use a DragHandler, which, when activated, calls startSystemMove.
Window {
width: 200; height: 100
color: '#fab'
flags: Qt.Window | Qt.FramelessWindowHint
DragHandler {
onActiveChanged: if(active) startSystemMove();
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
property int edges: 0;
property int edgeOffest: 5;
function setEdges(x, y) {
edges = 0;
if(x < edgeOffest) edges |= Qt.LeftEdge;
if(x > (width - edgeOffest)) edges |= Qt.RightEdge;
if(y < edgeOffest) edges |= Qt.TopEdge;
if(y > (height - edgeOffest)) edges |= Qt.BottomEdge;
}
cursorShape: {
return !containsMouse ? Qt.ArrowCursor:
edges == 3 || edges == 12 ? Qt.SizeFDiagCursor :
edges == 5 || edges == 10 ? Qt.SizeBDiagCursor :
edges & 9 ? Qt.SizeVerCursor :
edges & 6 ? Qt.SizeHorCursor : Qt.ArrowCursor;
}
onPositionChanged: setEdges(mouseX, mouseY);
onPressed: {
setEdges(mouseX, mouseY);
if(edges && containsMouse) {
startSystemResize(edges);
}
}
}
}
Preview
Final Notes
Still, I do not recommend developing a custom window with custom functionality, which forces you to handle a lot of functions while still not feeling like a native one.
However, there are a few github projects that offered some helper libraries for this, so take a look at those.
https://github.com/antonypro/QGoodWindow
https://github.com/wangwenx190/framelesshelper
I can't think of better way to do this
Here is a working example:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("Hello World")
flags: Qt.Window | Qt.FramelessWindowHint
Rectangle
{
id: dragItemRight
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 5
color: "red"
x: window.width - width
onXChanged:
{
if (dragItemRightMouse.drag.active)
{
window.width = dragItemRight.x + width
}
}
MouseArea
{
id: dragItemRightMouse
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAxis
cursorShape: Qt.SizeHorCursor
drag.minimumX: 300
drag.onActiveChanged:
{
if (!drag.active)
{
dragItemRight.x = Qt.binding(function() { return window.width - width })
}
}
}
}
Rectangle
{
id: dragItemBottom
anchors.left: parent.left
anchors.right: parent.right
height: 5
color: "red"
y: window.height - height
onYChanged:
{
if (dragItemBottomMouse.drag.active)
{
window.height = dragItemBottom.y + height
}
}
MouseArea
{
id: dragItemBottomMouse
anchors.fill: parent
drag.target: parent
drag.axis: Drag.YAxis
cursorShape: Qt.SizeVerCursor
drag.minimumY: 300
drag.onActiveChanged:
{
if (!drag.active)
{
dragItemBottom.y = Qt.binding(function() { return window.height - height })
}
}
}
}
Rectangle
{
id: dragItemBottomRight
width: 5
height: 5
color: "green"
x: window.width - width
y: window.height - height
onYChanged:
{
if (dragItemBottomRightMouse.drag.active)
{
window.height = dragItemBottomRight.y + height
}
}
onXChanged:
{
if (dragItemBottomRightMouse.drag.active)
{
window.width = dragItemBottomRight.x + width
}
}
MouseArea
{
id: dragItemBottomRightMouse
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAndYAxis
drag.minimumX: 300
drag.minimumY: 300
cursorShape: Qt.SizeFDiagCursor
drag.onActiveChanged:
{
if (!drag.active)
{
dragItemBottomRight.x = Qt.binding(function() { return window.width - width })
dragItemBottomRight.y = Qt.binding(function() { return window.height - height })
}
}
}
}
}
I found the solution:
import QtQuick
import QtQuick.Controls 2.15
import Qt5Compat.GraphicalEffects
import NR 1.0
Window {
id: mainWindow
width: 640
height: 720
visible: true
title: qsTr("Hello World")
flags: Qt.Window | Qt.FramelessWindowHint
color: "transparent"
property point startMousePos
property point startWindowPos
property size startWindowSize
function absoluteMousePos(mouseArea) {
var windowAbs = mouseArea.mapToItem(null, mouseArea.mouseX, mouseArea.mouseY)
return Qt.point(windowAbs.x + mainWindow.x,
windowAbs.y + mainWindow.y)
}
MouseArea {
id: moveArea
anchors.fill: title
property point mPos;
onPressed: {
mPos = Qt.point(mouseX, mouseY)
}
onPositionChanged: {
mainWindow.setX(mainWindow.x + mouseX - mPos.x)
mainWindow.setY(mainWindow.y + mouseY - mPos.y)
}
}
MouseArea {
id: leftArea
anchors.top: parent.top
anchors.topMargin: 48
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
cursorShape: Qt.SizeHorCursor
width: 5
onPressed: {
startMousePos = absoluteMousePos(leftArea)
startWindowPos = Qt.point(mainWindow.x, mainWindow.y)
startWindowSize = Qt.size(mainWindow.width, mainWindow.height)
}
onMouseXChanged: {
var abs = absoluteMousePos(leftArea)
var newWidth = Math.max(mainWindow.minimumWidth, startWindowSize.width - (abs.x - startMousePos.x))
var newX = startWindowPos.x - (newWidth - startWindowSize.width)
mainWindow.x = newX
mainWindow.width = newWidth
}
Rectangle {
anchors.fill: parent
color: "red"
}
}
MouseArea {
id: rightArea
width: 5
x: parent.width - rightArea.width
anchors.right: parent.rigth
anchors.top: parent.top
anchors.rightMargin: 5
anchors.topMargin: 48
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
cursorShape: Qt.SizeHorCursor
onPressed: {
startMousePos = absoluteMousePos(rightArea)
startWindowPos = Qt.point(mainWindow.x, mainWindow.y)
startWindowSize = Qt.size(mainWindow.width, mainWindow.height)
}
onMouseXChanged: {
var abs = absoluteMousePos(rightArea)
var newWidth = Math.max(mainWindow.minimumWidth, startWindowSize.width + (abs.x - startMousePos.x))
mainWindow.width = newWidth
}
Rectangle {
anchors.fill: parent
color: "red"
}
}
MouseArea {
id: buttonArea
y: parent.height - buttonArea.height
height: 5
anchors.leftMargin: 5
anchors.left: parent.left
anchors.rightMargin: 5
anchors.right: parent.right
anchors.bottom: parent.bottom
cursorShape: Qt.SizeVerCursor
onPressed: {
startMousePos = absoluteMousePos(buttonArea)
startWindowPos = Qt.point(mainWindow.x, mainWindow.y)
startWindowSize = Qt.size(mainWindow.width, mainWindow.height)
}
onMouseYChanged: {
var abs = absoluteMousePos(buttonArea)
var newHeight = Math.max(mainWindow.minimumHeight, startWindowSize.height + (abs.y - startMousePos.y))
mainWindow.height = newHeight
}
Rectangle {
anchors.fill: parent
color: "red"
}
}
}
I have a TextArea and popup or another item which overlaps it. But when I pointing at popup, cursor shape doesn't change. I need the cursor to become default when I pointing to overlapped items.
Code:
import QtQuick 2.7
import QtQuick.Controls 2.1
ApplicationWindow {
id: root
visible: true
width: 800
height: 600
Component.onCompleted: pop.open()
TextArea {
width: 800
height: 600
}
Popup {
id: pop
Rectangle {
color: "red"
width: 100
height: 100
}
MouseArea {
anchors.fill: parent
}
}
}
The TextArea contains a MouseArea that sets a different cursor shape.
The cursor shape is always defined by the top-most MouseArea. Therefore the solution is, to add a MouseArea to the overlapping Item to reset the cursor shape for this area.
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow {
id: root
visible: true
width: 800
height: 600
TextArea {
width: 800
height: 600
}
Rectangle {
color: 'red'
width: 100
height: 100
x: 100
y: 50
MouseArea { // This resets the cursor shape, if the cursor hovers over the Rectangle
anchors.fill: parent
}
}
}
The issue has been fixed in Qt 5.9.
Thanks to jpnurmi
I want to do something very similar to this image in Qt where I can click in any square and change the color of it.
It's pretty simple to do this with QML. Look at the code below:
import QtQuick 2.1
import QtQuick.Window 2.0
Window {
id: root
visible: true
width: 360
height: 500
Column{
Repeater{
model: getRowsNumber(root.height)
delegate: Row{
property int externalIdx: index
Repeater{
model: getColumnsNumber(root.width)
delegate: Rectangle{
property bool selected: false
property color originalColor: (index + externalIdx) % 2 == 0 ? "black" : "white"
width:20
height: 20
color: selected ? "red" : originalColor
border.width: 1
border.color: "black"
MouseArea{
anchors.fill: parent
onClicked: parent.selected = !parent.selected
}
}
}
}
}
}
function getColumnsNumber(width){
return width/20;
}
function getRowsNumber(height){
return height/20;
}
}
That's all you need to have a rectangular chess-like grid where each cell changes its color when it is clicked on. Of course, you will need to adapt it to your needs but that should be enough for you to start.
I`m try to get control under cursor. At my example i can get only red rectangle, but i need get other also.
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: mainWindow
title: qsTr("Hello World")
width: 640
height: 480
Item {
id: parentPanel
anchors.fill: parent
MouseArea {
anchors.fill: parent
hoverEnabled: true
onMouseXChanged: moveMouse()
onMouseYChanged: moveMouse()
function moveMouse()
{
currentControl.text = parentPanel.childAt(mouseX, mouseY).color ? parentPanel.childAt(mouseX, mouseY).color : "not colored"
}
}
Rectangle {
id: redRect
anchors {
fill: parent
leftMargin: 50
bottomMargin: 50
}
color: "red"
Rectangle {
id: yellowRect
anchors {
fill: parent
leftMargin: 50
bottomMargin: 50
}
color: "yellow"
Rectangle {
id: greenRect
anchors {
fill: parent
leftMargin: 50
bottomMargin: 50
}
color: "green"
}
}
}
Text {
id: currentControl
anchors.left: parent.left
anchors.bottom: parent.bottom
}
}
}
I have screenshot from running program. Green rect inside yellow, yellow inside red. I need get control ref when mouse cursor over control.
I'm not familiar with QML, so I don't know the exact syntax for this, but it seems like you want to loop until you find the inner-most control and get the color of that. Here's some C++ish pseudo-code
auto control = parentPanel.childAt(mouseX, mouseY);
while (control)
{
currentControl.text = control.color ? control.color : "not colored";
control = control.childAt(mouseX, mouseY);
}
Of course, this code assumes that the X and Y passed are absolute, not relative. If the are relative, you would need to decrement them by the location of control in each consecutive loop.
I am writing a qml application that should support RTL and LTR languages and the interface needs to be some how flexible, and anchors may not produce good UI
So I planned to use qml Grid, Column, and RowLayout, they work good but does not get mirrored when I use
LayoutMirroring.enabled: true
LayoutMirroring.childrenInherit: true
is there any way to use these layout components with LayoutMirroring.enabled: true
and if not how to set width and height for qml positioners (Row,Column,and Grid) to fill thier bounding item width and height
LayoutMirroring is not available for RowLayout, ColumnLayout or GridLayout. You can use Row[View], Column[View] or Grid[View] instead. See http://qt-project.org/doc/qt-5.1/qtquick/qml-qtquick2-layoutmirroring.html for more information.
Here is a quick example for qml positioners:
Rectangle {
width: 640
height: 480
color: "#303030"
Rectangle {
width: parent.width / 1.1
height: parent.height / 1.1
anchors.centerIn: parent
color: "white"
Grid {
id: grid
anchors.fill: parent
columns: 2
spacing: 6
columnSpacing: spacing
rowSpacing: spacing
property int rowCount: Math.ceil(visibleChildren.length / columns)
property real cellWidth: (width - (columns - 1) * columnSpacing) / columns
property real cellHeight: (height - (rowCount - 1) * rowSpacing) / rowCount
Rectangle {
color: "#aa6666"
width: grid.cellWidth
height: grid.cellHeight
}
Rectangle {
color: "#aaaa66"
width: grid.cellWidth
height: grid.cellHeight
}
Rectangle {
color: "#9999aa"
width: grid.cellWidth
height: grid.cellHeight
}
Rectangle {
color: "#6666aa"
width: grid.cellWidth
height: grid.cellHeight
}
}
}
}
Change the number of columns and add or remove some rectangles, to see that it works.
in Qt 5.2 RowLayout, ColumnLayout and GridLayout have layoutDirection property to support RTL layout