Trying to make a very simple node editor in Qt Quick.
The most tricky part seems to be the Connection item, which has to track the two absolute positions of the source and destination IOPorts.
The following code works fine, except that the bindings are not evaluated (or are evaluated in the wrong order) at startup, resulting in the connection line to be in the wrong position at startup:
which is then adjusted to the correct position as soon as either node is moved:
I thought about adding:
Component.onCompleted: {
start = mapFromItem(from, 0, 0)
end = mapFromItem(to, 0, 0)
}
to Connection, which fixes the startup problem, but breaks the bindings.
So I resorted to this ugly hack:
Component.onCompleted: {
from.x = from.x+1
from.x = from.x-1
}
Is there a better solution to this problem?
(files available in GitHub repository, link at bottom)
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 420
height: 280
visible: true
title: qsTr("Node editor")
Node {
x: 20
y: 40
width: 120
height: 50
IOPort {id: o1; x: 0; y: parent.height}
}
Node {
x: 260
y: 140
width: 120
height: 50
IOPort {id: i1; x: 0; y: 0}
}
Connection {
from: o1; to: i1
}
}
Node.qml:
import QtQuick 2.0
Rectangle {
id: rect
border.color: "black"
border.width: 2
color: "#eee"
MouseArea {
anchors.fill: parent
drag.target: rect
}
}
IOPort.qml:
import QtQuick 2.0
Item {
id: root
property real size: 15
property real globalX: x + parent.x
property real globalY: y + parent.y
property real globalZ: 1 + parent.z
Rectangle {
id: rect
x: -width/2
y: -height/2
width: root.size
height: root.size
border.color: "black"
color: "gray"
}
}
Connection.qml:
import QtQuick 2.0
import QtQuick.Shapes 1.15
Shape {
id: root
property IOPort from
property IOPort to
x: Math.min(from.globalX, to.globalX)
y: Math.min(from.globalY, to.globalY)
z: Math.max(from.globalZ, to.globalZ) + 0.5
width: Math.abs(from.globalX - to.globalX)
height: Math.abs(from.globalY - to.globalY)
property point start: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(from, 0, 0)
property point end: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(to, 0, 0)
ShapePath {
strokeWidth: 4
strokeColor: "black"
startX: root.start.x
startY: root.start.y
PathLine {
x: root.end.x
y: root.end.y
}
}
}
GitHub repository: https://github.com/fferri/qtquick-simple-node-editor.git
I cannot currently reproduce the error but I think I found a problem:
Your Connection root's start and end properties are missing a "forced" dependency on that same object's x and y
For some reason you decided to not only to set the coordinates for path's points but also move the whole Connection element itself. So if root's x and y happen to update after start and end did, the latter still contain relative coordinates of the point when x and y were still default (probably 0,0) and thus the line becomes displaced by x,y pixels.
Try changing them this way (I also removed dependencies you shouldn't need):
property point start: from.globalX, from.globalY, x, y, mapFromItem(from, 0, 0)
property point end: x, y, to.globalX, to.globalY, mapFromItem(to, 0, 0)
(There were more suggestions here in case this didn't work but they were removed because the above seems to have solved the issue)
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 days ago.
Improve this question
I developed software with python and Qml and I want to display a sequence of 2D images (100 slices) in a 3D space with Qml in this software. I think Qt3D or QtQuick 3D modules are proper for that purpose because the following code displays an image in 3D space but cannot figure out how to display a sequence of images.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick3D 1.14
Window {
id: window
width: 640
height: 640
visible: true
color: "black"
Rectangle {
id: qt_logo
width: 230
height: 230
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 10
color: "black"
property int angle: 0
layer.enabled: true
Image {
anchors.fill: parent
source: "qt_logo.png"
}
}
View3D {
id: view
anchors.fill: parent
camera: camera
renderMode: View3D.Overlay
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 200, -300)
rotation: Qt.vector3d(30, 0, 0)
}
DirectionalLight {
rotation: Qt.vector3d(30, 0, 0)
}
Model {
id: cube
visible: true
position: Qt.vector3d(0, 0, 0)
source: "#Rectangle"
materials: [ DefaultMaterial {
diffuseMap: Texture {
id: texture
sourceItem: qt_logo
flipV: true
}
}
]
rotation: Qt.vector3d(0, 0, 0)
}
}
}
Edit: I searched a little more and I found the texture3D in the Qt3d module could be a better option for my purpose. But I can't find any example of texture3D in qml. Now the following questions are my problems? 1. How I can generate a texture3D from a stack of 2D images? 2. How I can display a texture3D in qml. 3. Can I generate input data manually for Texture3D (Like 3D array)?
Thank you for your help.
If you're using Texture you can set sourceItem to place any 2D component, including, Rectangle and Image onto a 3D object, such as a "#Cube".
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick3D
Page {
background: Rectangle { color: "#848895" }
Node {
id: standAloneScene
DirectionalLight { ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0) }
Node {
id: node
Model {
source: "#Cube"
materials: [
DefaultMaterial {
diffuseMap: Texture {
sourceItem: MyItem { }
}
}
]
}
}
OrthographicCamera {
id: cameraOrthographicFront
lookAtNode: node
y: 800; z: 1000
}
}
View3D {
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
NumberAnimation {
target: node
property: "eulerRotation.y"
loops: Animation.Infinite
running: true
from: 720; to: 0
duration: 10000
}
}
// MyItem.qml
import QtQuick
import QtQuick.Controls
Rectangle {
width: 256
height: 256
color: "#78a"
Repeater {
model: 5
Image {
x: index * 20 + 20
y: index * 20 + 20
width: 128
height: 128
source: "https://stephenquan.github.io/images/qt/madewithqt.png"
}
}
}
You can Try it Online!
So I'm trying to create an application in QML, and doing so by using an ApplicationWindow. The following code is in main.qml, where some of the components are defined in other files (they are not essential for this problem):
import QtQuick 2.12
import QtQuick.Extras 1.4
import QtQuick.Controls 2.5
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.3
import QtDataVisualization 1.3
import Qt3D.Core 2.9
import Qt3D.Render 2.9
import QtCharts 2.3
ApplicationWindow {
id: window
width: 1280
height: 720
//visibility: ApplicationWindow.FullScreen
visible: true
color: "#444444"
Speedometer {
id: speedometer
x: 49
y: 144
width: 306
height: 320
minValue: 0
maxValue: 600
visible: true
}
Thermometer {
id: thermometer
x: 1170
y: 233
scale: 2
minValue: 0
maxValue: 50
visible: true
}
Slider {
id: slider
from: 0
to: 100
x: 28
y: 594
width: 1224
height: 98
font.pointSize: 14
hoverEnabled: false
enabled: false
live: true
snapMode: Slider.NoSnap
value: 0
visible: true
Behavior on value {
NumberAnimation {
duration: 200
}
}
background: Rectangle {
x: slider.leftPadding
y: slider.topPadding + slider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: slider.availableWidth
height: implicitHeight
radius: 2
color: "#999999"
}
handle: Rectangle {
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
y: slider.topPadding + slider.availableHeight / 2 - height / 2
implicitWidth: 26
implicitHeight: 26
radius: 13
color: "#0099ff"
}
}
Timer {
interval: 200
running: true
repeat: true
property var distance: Math.random
onTriggered: update()
}
function update(){
var distance = (Math.random() * 0.03) + 0.1;
var speed = (distance * 50) / 0.02;
slider.value = slider.value + distance;
speedometer.value = speed;
thermometer.value = Math.random() * 25 + 25;
}
DataManager {
id: manager
onNewVelocity: {
lineSeries.append(velocity.x, velocity.y)
chartView.title = name
}
}
Timer {
id: timer
repeat: true
interval: 1
onTriggered: {
manager.dummyData();
}
}
Chart {
id: chart
height: 250
width: 250
x: 1000
}
Text {
id: labelText
color: "white"
width: 300
height: 100
}
Button {
id: startThread
text: "Start"
onClicked: {
timer.start()
}
}
Button {
id: stopThread
text: "Stop"
x: 200
onClicked: {
timer.stop()
}
}
Button {
id: clearGraph
text: "Clear"
x: 100
onClicked: {
lineSeries.clear()
}
}
}
The content itself isn't that important, but the thing that is, is the fact that when I personally run the project, the application window doesn't show what it is supposed to. And I'd like to emphasize: I'm collaborating with others on this exact project, and when they run the exact same code as me, they get different results.
The first image is what I get when running the code:
The second image is what they get, and what is actually supposed to show when running the code:
So I've been trying to search Google for every possible solution, but have found nothing. I've reinstalled Qt and QtCreator, but to no prevail. We are running the exact same thing, but I'm the only one that don't see all components show up. The first image is way more zoomed in than the latter, and I'd really like if anyone knew how to fix this. I've struggled to find anything that could help so far
(Extra note: the code I ran didn't include the graph as it wasn't up to date with current version on git, but the other things remain the same. So the bar below and the thermometer component on the right SHOULD be visible, but they're not).
I eventually found a solution to what seems to be a bug from Qt's end. I had to launch QtCreator via a qtcreator.bat file in the same directory as qtcreator.exe with the following content:
#echo off
set QT_SCALE_FACTOR_ROUNDING_POLICY=Round
set QT_AUTO_SCREEN_SCALE_FACTOR=0
qtcreator.exe
When I now run the project, it shows the correct scale :)
I have a Qt QML application. Following is the complete code of the application:
Code:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: app_main_window
visible: true
width: 800
height: 600
title: qsTr("Hello QML")
Rectangle {
id: my_item_1
x: 100
y: 100
width: 100
height: 100
color: "grey"
visible: true
z: 1
}
Rectangle {
id: my_item_2
x: 400
y: 400
width: 100
height: 100
color: "grey"
visible: true
z: 1
MouseArea {
anchors.fill: parent
onClicked: {
// How can I change the center of my_item_1 to some arbitrary new center. lets say app_main_window's center. this involves another question. how can I get app_main_window's center?
}
}
}
}
Question:
My question is simple. How can change the center of any QQuickitem to a new center? So in above case how can change the center of my_item_1 to a new center (Qt.point(some_x_center, some_y_center)) when my_item_2 gets a click event.
Additionally, is possible to get another item's center? Like app_main_window or my_item_2's center to apply to the target my_item_1?
PS:
I have made the code simple to make the question objective. I have quite a complicated logic in my actual code where I need to realign something like my_item_1 to a new center without usage of anchors as the QQuickitem I am trying to do that with is scaled and panned into a new point.
If anchors can't be used, you have to calculate manually the center and assign it.
Quick example:
Item
{
anchors.fill: parent
Rectangle
{
id: nonParentOrSibling
width: 200
height: 200
x: 350
y: 240
color: "red"
}
}
Rectangle
{
id: rectToMove
width: 100
height: 100
y: 200
color: "blue"
MouseArea
{
anchors.fill: parent
onClicked:
{
var itemCenter = Qt.point(nonParentOrSibling.x + nonParentOrSibling.width / 2, nonParentOrSibling.y + nonParentOrSibling.height / 2)
rectToMove.x = itemCenter.x - rectToMove.width / 2
rectToMove.y = itemCenter.y - rectToMove.height / 2
}
}
}
Note that this is a one time moving only, and the rectangle won't move if the target is moved.
To make it follow the target you have to rebind it with Qt.binding.
If rectToMove is not in the same coordinate system as nonParentOrSibling, you can use Item.mapToItem() and mapFromItem() to adapt the coordinates.
In this Qt Quick simple example, I want that when I move a Racked upwards, we have a message on console.log showing that movement and another message when the Racket is moved downwards. I've written this code for that:
Racket.qml:
import QtQuick 2.8
Rectangle {
id: root
width: 15; height: 50
x: 400; y: 100
color: "red"
property int oldY: 100
property bool yUwards: false
property bool yDwards: false
onYChanged: {
if( y > oldY) {
yDwards = true
yUwards = false
console.log("Racket moved downwards.\n")
}
else if( y < oldY) {
yDwards = false
yUwards = true
console.log("Racket moved upwards.\n")
}
oldY = y
}
MouseArea {
anchors.fill: parent
drag.target: root
drag.axis: Drag.YAxis
drag.minimumY: 10
drag.maximumY: 440
}
}
main.qml:
import QtQuick 2.8
import QtQuick.Window 2.2
Window {
visible: true
width: 800
height: 600
Rectangle {
id: table
width: 700; height: 500
border.width: 10
border.color: "black"
color: "white"
Racket {
id: redRacket
x: 630; y: 100
color: "red"
}
}
}
When I at the first time hit Run, the code didn't work, so I hit clean all, run qmake and then rebuild all, then it worked!
How does a new QML programmer like me figure out that the code is fine but this process is needed to successfully run the program?
This is indeed a bug in QMake.
As #Mitch posted in his comment, a bug report was already filed: bugreports.qt.io/browse/QTBUG-13334
If you read through the comments in that bug report, a workaround for the problem was posted. Have been using it for a couple of months now and it seems to fix the issue.
All you have to do is paste this at the end of your pro file.
#update qml
qml_scenes.depends = $$PWD/resources/scene1.qml $$PWD/resources/scene2.qml ...
qml_scenes.commands =
QMAKE_EXTRA_TARGETS += qml_scenes
I have a GridView in QML ApplicationWindow which should be filled
with some Items.
I place my items with JS function "placeItems".
But the problem is that when Component.onCreated signal of ApplicationWindow is called the GridView is not yet layouted.
For example, the GridView has x coordinate equal to -425 in Component.onCreated of ApplicationWindow.
If I call the same function a second later - everything is ok and GridView
has correct coordinates (=75).
I've check the Qt reference back and forth and haven't found other signals (something like onLayouted or onLayoutComplete) that may be helpful.
The question is when to call "placeItems" so the GridView in ApplicationWindow
already has correct coordinates?
UPDATE1:
To observe the bad behaviour just click File->Start after the application started. It will place the item in the correct place.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
function max (a,b) { return a>b ? a : b; }
function min (a,b) { return a<b ? a : b; }
property int sizeMin: width < height ? width : height
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
property var items: []
function centerCell(column,row) {
return {x: field.x + (column + 0.5) * cellWidth,
y: field.y + (row + 0.5) * cellHeight}
}
function placeItem(name, col, row) {
var c = centerCell(col,row)
items[name].centerX = c.x
items[name].centerY = c.y
}
function placeItems() {
placeItem ("a", 3, 3)
//placeItem ("b", 4, 4)
}
}
Component.onCompleted: field.placeItems()
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int centerX: 0
property int centerY: 0
property int margin: 5
property var cell: field.items["a"] = this
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
x: centerX - width/2
y: centerY - height/2
color: "red"
opacity: 0.5
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Start")
onTriggered: field.placeItems();
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
function placeItem(name, col, row) {
items[name].anchors.horizontalCenter = field.left;
items[name].anchors.verticalCenter = field.top;
items[name].anchors.horizontalCenterOffset = (col + 0.5) * cellWidth;
items[name].anchors.verticalCenterOffset = (row + 0.5) * cellHeight;
}
The key is to anchor the element in the grid view and then move it according to your calculations.
BTW, you know that QML has built in functions Math.min/Math.max?
EDIT
Or better yet, why not define the bindings in rectItemA directly?
Another, less hackish way to have the right behavior (don't play with Timer with layout, really, it's a bad idea):
You are defining your Rectangle as an item centered in a instance of a item belonging to your GridView. So, I use a little of your way (getting an item at the r row and the c column in the gridview), and then I reparent the Rectangle to this item. To make it centered, it is only needed to anchor it to the center of its newly bound parent.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
property int sizeMin: Math.min(width, height)
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
function cellAt(row, col) {
return itemAt(row * (dimCell + dimCellSpacing), col * (dimCell + dimCellSpacing));
}
}
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int margin: 5
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
anchors.centerIn: parent
color: "red"
opacity: 0.5
}
Component.onCompleted: {
rectItemA.parent = field.cellAt(3, 3);
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
Why don't you just delay the placeItems function so it runs with a tiny delay so that when it runs the "static" components are all completed.
Timer {
interval: 250 // might want to tune that
repeat: false
onTriggered: placeItems()
}
In a perfect world, Component.onCompleted would nest perfectly and the root item would be the last one to be emitted. But unfortunately Qt does not guarantee the order, and indeed as I did a quick test, the parent item emits (or at least responds to) onCompleted BEFORE the child items.
And if you don't want to pollute your QML file with the timer, you can actually instantiate a "helper object" from a JS function dynamically, set it to do its work and then delete itself when done. Similar to the "reparent helper" I outlined in this answer: Better way to reparent visual items in QML but rather delete itself on the timer timeout rather than in the JS function which would not give it the time to trigger.