QmlDesigner: Add ScaleGizmo to 3D edit view

ScaleGizmo allows scaling in the direction of local or global axes,
as well as uniform scaling. Any scale component cannot be made
negative with ScaleGizmo.

Change-Id: I9b98d9593e07ded340178b07b73fa1b72421ba20
Fixes: QDS-1195
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2019-10-31 10:46:14 +02:00
parent 96f3cef06a
commit c67965fb29
12 changed files with 561 additions and 138 deletions

View File

@@ -27,104 +27,29 @@ import QtQuick 2.0
import QtQuick3D 1.0 import QtQuick3D 1.0
import MouseArea3D 1.0 import MouseArea3D 1.0
Model { DirectionalDraggable {
id: arrow id: arrow
rotationOrder: Node.XYZr
source: "meshes/arrow.mesh" source: "meshes/arrow.mesh"
property View3D view3D
property alias color: material.emissiveColor
property Node targetNode: null
property bool dragging: false
readonly property bool hovering: mouseAreaYZ.hovering || mouseAreaXZ.hovering
property var _pointerPosPressed
property var _targetStartPos
signal positionCommit() signal positionCommit()
signal positionMove() signal positionMove()
materials: DefaultMaterial { function localPos(sceneRelativeDistance)
id: material
emissiveColor: "white"
lighting: DefaultMaterial.NoLighting
}
function handlePressed(mouseArea, pointerPosition)
{ {
if (!targetNode)
return;
var maskedPosition = Qt.vector3d(pointerPosition.x, 0, 0);
_pointerPosPressed = mouseArea.mapPositionToScene(maskedPosition);
var sp = targetNode.scenePosition;
_targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z);
dragging = true;
}
function posInParent(mouseArea, pointerPosition)
{
var maskedPosition = Qt.vector3d(pointerPosition.x, 0, 0);
var scenePointerPos = mouseArea.mapPositionToScene(maskedPosition);
var sceneRelativeDistance = Qt.vector3d(
scenePointerPos.x - _pointerPosPressed.x,
scenePointerPos.y - _pointerPosPressed.y,
scenePointerPos.z - _pointerPosPressed.z);
var newScenePos = Qt.vector3d( var newScenePos = Qt.vector3d(
_targetStartPos.x + sceneRelativeDistance.x, _targetStartPos.x + sceneRelativeDistance.x,
_targetStartPos.y + sceneRelativeDistance.y, _targetStartPos.y + sceneRelativeDistance.y,
_targetStartPos.z + sceneRelativeDistance.z); _targetStartPos.z + sceneRelativeDistance.z);
return targetNode.parent.mapPositionFromScene(newScenePos); return targetNode.parent.mapPositionFromScene(newScenePos);
} }
function handleDragged(mouseArea, pointerPosition) onDragged: {
{ targetNode.position = localPos(sceneRelativeDistance);
if (!targetNode) positionMove();
return;
targetNode.position = posInParent(mouseArea, pointerPosition);
arrow.positionMove();
} }
function handleReleased(mouseArea, pointerPosition) onReleased: {
{ targetNode.position = localPos(sceneRelativeDistance);
if (!targetNode) positionCommit();
return;
targetNode.position = posInParent(mouseArea, pointerPosition);
dragging = false;
arrow.positionCommit();
}
MouseArea3D {
id: mouseAreaYZ
view3D: arrow.view3D
x: 0
y: -1.5
width: 12
height: 3
rotation: Qt.vector3d(0, 0, 90)
grabsMouse: targetNode
onPressed: arrow.handlePressed(mouseAreaYZ, pointerPosition)
onDragged: arrow.handleDragged(mouseAreaYZ, pointerPosition)
onReleased: arrow.handleReleased(mouseAreaYZ, pointerPosition)
}
MouseArea3D {
id: mouseAreaXZ
view3D: arrow.view3D
x: 0
y: -1.5
width: 12
height: 3
rotation: Qt.vector3d(0, 90, 90)
grabsMouse: targetNode
onPressed: arrow.handlePressed(mouseAreaXZ, pointerPosition)
onDragged: arrow.handleDragged(mouseAreaXZ, pointerPosition)
onReleased: arrow.handleReleased(mouseAreaXZ, pointerPosition)
} }
} }

View File

@@ -0,0 +1,124 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.0
import QtQuick3D 1.0
import MouseArea3D 1.0
Model {
id: rootModel
rotationOrder: Node.XYZr
property View3D view3D
property alias color: material.emissiveColor
property Node targetNode: null
property bool dragging: false
property bool active: false
readonly property bool hovering: mouseAreaYZ.hovering || mouseAreaXZ.hovering
property var _pointerPosPressed
property var _targetStartPos
signal pressed(var mouseArea)
signal dragged(var mouseArea, vector3d sceneRelativeDistance)
signal released(var mouseArea, vector3d sceneRelativeDistance)
materials: DefaultMaterial {
id: material
emissiveColor: "white"
lighting: DefaultMaterial.NoLighting
}
function handlePressed(mouseArea, scenePos)
{
if (!targetNode)
return;
var maskedPosition = Qt.vector3d(scenePos.x, 0, 0);
_pointerPosPressed = mouseArea.mapPositionToScene(maskedPosition);
var sp = targetNode.scenePosition;
_targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z);
dragging = true;
pressed(mouseArea);
}
function calcRelativeDistance(mouseArea, scenePos)
{
var maskedPosition = Qt.vector3d(scenePos.x, 0, 0);
var scenePointerPos = mouseArea.mapPositionToScene(maskedPosition);
return Qt.vector3d(scenePointerPos.x - _pointerPosPressed.x,
scenePointerPos.y - _pointerPosPressed.y,
scenePointerPos.z - _pointerPosPressed.z);
}
function handleDragged(mouseArea, scenePos)
{
if (!targetNode)
return;
dragged(mouseArea, calcRelativeDistance(mouseArea, scenePos));
}
function handleReleased(mouseArea, scenePos)
{
if (!targetNode)
return;
released(mouseArea, calcRelativeDistance(mouseArea, scenePos));
dragging = false;
}
MouseArea3D {
id: mouseAreaYZ
view3D: rootModel.view3D
x: 0
y: -1.5
width: 12
height: 3
rotation: Qt.vector3d(0, 0, 90)
grabsMouse: targetNode
active: rootModel.active
onPressed: rootModel.handlePressed(mouseAreaYZ, scenePos)
onDragged: rootModel.handleDragged(mouseAreaYZ, scenePos)
onReleased: rootModel.handleReleased(mouseAreaYZ, scenePos)
}
MouseArea3D {
id: mouseAreaXZ
view3D: rootModel.view3D
x: 0
y: -1.5
width: 12
height: 3
rotation: Qt.vector3d(0, 90, 90)
grabsMouse: targetNode
active: rootModel.active
onPressed: rootModel.handlePressed(mouseAreaXZ, scenePos)
onDragged: rootModel.handleDragged(mouseAreaXZ, scenePos)
onReleased: rootModel.handleReleased(mouseAreaXZ, scenePos)
}
}

View File

@@ -48,8 +48,8 @@ Window {
property var cameraGizmos: [] property var cameraGizmos: []
signal objectClicked(var object) signal objectClicked(var object)
signal commitObjectPosition(var object) signal commitObjectProperty(var object, var propName)
signal moveObjectPosition(var object) signal changeObjectProperty(var object, var propName)
function selectObject(object) { function selectObject(object) {
selectedNode = object; selectedNode = object;
@@ -113,11 +113,26 @@ Window {
position: viewWindow.selectedNode ? viewWindow.selectedNode.scenePosition position: viewWindow.selectedNode ? viewWindow.selectedNode.scenePosition
: Qt.vector3d(0, 0, 0) : Qt.vector3d(0, 0, 0)
globalOrientation: globalControl.checked globalOrientation: globalControl.checked
visible: selectedNode visible: selectedNode && moveToolControl.checked
view3D: overlayView view3D: overlayView
onPositionCommit: viewWindow.commitObjectPosition(selectedNode) onPositionCommit: viewWindow.commitObjectProperty(selectedNode, "position")
onPositionMove: viewWindow.moveObjectPosition(selectedNode) onPositionMove: viewWindow.changeObjectProperty(selectedNode, "position")
}
ScaleGizmo {
id: scaleGizmo
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
highlightOnHover: true
targetNode: viewWindow.selectedNode
position: viewWindow.selectedNode ? viewWindow.selectedNode.scenePosition
: Qt.vector3d(0, 0, 0)
globalOrientation: globalControl.checked
visible: selectedNode && scaleToolControl.checked
view3D: overlayView
onScaleCommit: viewWindow.commitObjectProperty(selectedNode, "scale")
onScaleChange: viewWindow.changeObjectProperty(selectedNode, "scale")
} }
AutoScaleHelper { AutoScaleHelper {
@@ -186,11 +201,11 @@ Window {
Overlay2D { Overlay2D {
id: gizmoLabel id: gizmoLabel
targetNode: moveGizmo targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo
targetView: overlayView targetView: overlayView
offsetX: 0 offsetX: 0
offsetY: 45 offsetY: 45
visible: moveGizmo.dragging visible: targetNode.dragging
Rectangle { Rectangle {
color: "white" color: "white"
@@ -203,11 +218,18 @@ Window {
id: gizmoLabelText id: gizmoLabelText
text: { text: {
var l = Qt.locale(); var l = Qt.locale();
selectedNode var targetProperty;
? qsTr("x:") + Number(selectedNode.position.x).toLocaleString(l, 'f', 1) if (viewWindow.selectedNode) {
+ qsTr(" y:") + Number(selectedNode.position.y).toLocaleString(l, 'f', 1) if (gizmoLabel.targetNode === moveGizmo)
+ qsTr(" z:") + Number(selectedNode.position.z).toLocaleString(l, 'f', 1) targetProperty = viewWindow.selectedNode.position;
: ""; else
targetProperty = viewWindow.selectedNode.scale;
return qsTr("x:") + Number(targetProperty.x).toLocaleString(l, 'f', 1)
+ qsTr(" y:") + Number(targetProperty.y).toLocaleString(l, 'f', 1)
+ qsTr(" z:") + Number(targetProperty.z).toLocaleString(l, 'f', 1);
} else {
return "";
}
} }
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -252,6 +274,19 @@ Window {
text: qsTr("Use Global Orientation") text: qsTr("Use Global Orientation")
onCheckedChanged: cameraControl.forceActiveFocus() onCheckedChanged: cameraControl.forceActiveFocus()
} }
Column {
x: 8
RadioButton {
id: moveToolControl
checked: false
text: qsTr("Move Tool")
}
RadioButton {
id: scaleToolControl
checked: true
text: qsTr("Scale Tool")
}
}
} }
Text { Text {

View File

@@ -51,6 +51,7 @@ Node {
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1))
: Qt.rgba(1, 0, 0, 1) : Qt.rgba(1, 0, 0, 1)
view3D: moveGizmo.view3D view3D: moveGizmo.view3D
active: moveGizmo.visible
onPositionCommit: moveGizmo.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: moveGizmo.positionMove() onPositionMove: moveGizmo.positionMove()
@@ -61,9 +62,10 @@ Node {
objectName: "Arrow Y" objectName: "Arrow Y"
rotation: Qt.vector3d(0, 0, 0) rotation: Qt.vector3d(0, 0, 0)
targetNode: moveGizmo.targetNode targetNode: moveGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1))
: Qt.rgba(0, 0, 1, 1) : Qt.rgba(0, 0.6, 0, 1)
view3D: moveGizmo.view3D view3D: moveGizmo.view3D
active: moveGizmo.visible
onPositionCommit: moveGizmo.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: moveGizmo.positionMove() onPositionMove: moveGizmo.positionMove()
@@ -74,9 +76,10 @@ Node {
objectName: "Arrow Z" objectName: "Arrow Z"
rotation: Qt.vector3d(90, 0, 0) rotation: Qt.vector3d(90, 0, 0)
targetNode: moveGizmo.targetNode targetNode: moveGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1))
: Qt.rgba(0, 0.6, 0, 1) : Qt.rgba(0, 0, 1, 1)
view3D: moveGizmo.view3D view3D: moveGizmo.view3D
active: moveGizmo.visible
onPositionCommit: moveGizmo.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: moveGizmo.positionMove() onPositionMove: moveGizmo.positionMove()
@@ -108,13 +111,14 @@ Node {
rotation: view3D.camera.rotation rotation: view3D.camera.rotation
grabsMouse: moveGizmo.targetNode grabsMouse: moveGizmo.targetNode
priority: 1 priority: 1
active: moveGizmo.visible
property var _pointerPosPressed property var _pointerPosPressed
property var _targetStartPos property var _targetStartPos
function posInParent(pointerPosition) function localPos(scenePos)
{ {
var scenePointerPos = mapPositionToScene(pointerPosition); var scenePointerPos = mapPositionToScene(scenePos);
var sceneRelativeDistance = Qt.vector3d( var sceneRelativeDistance = Qt.vector3d(
scenePointerPos.x - _pointerPosPressed.x, scenePointerPos.x - _pointerPosPressed.x,
scenePointerPos.y - _pointerPosPressed.y, scenePointerPos.y - _pointerPosPressed.y,
@@ -132,7 +136,7 @@ Node {
if (!moveGizmo.targetNode) if (!moveGizmo.targetNode)
return; return;
_pointerPosPressed = mapPositionToScene(pointerPosition); _pointerPosPressed = mapPositionToScene(scenePos);
var sp = moveGizmo.targetNode.scenePosition; var sp = moveGizmo.targetNode.scenePosition;
_targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z); _targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z);
} }
@@ -140,14 +144,14 @@ Node {
if (!moveGizmo.targetNode) if (!moveGizmo.targetNode)
return; return;
moveGizmo.targetNode.position = posInParent(pointerPosition); moveGizmo.targetNode.position = localPos(scenePos);
moveGizmo.positionMove(); moveGizmo.positionMove();
} }
onReleased: { onReleased: {
if (!moveGizmo.targetNode) if (!moveGizmo.targetNode)
return; return;
moveGizmo.targetNode.position = posInParent(pointerPosition); moveGizmo.targetNode.position = localPos(scenePos);
moveGizmo.positionCommit(); moveGizmo.positionCommit();
} }
} }

View File

@@ -0,0 +1,159 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.0
import QtQuick3D 1.0
import MouseArea3D 1.0
Node {
id: scaleGizmo
property View3D view3D
property bool highlightOnHover: false
property Node targetNode: null
property bool globalOrientation: true
readonly property bool dragging: scaleRodX.dragging || scaleRodY.dragging || scaleRodZ.dragging
|| centerMouseArea.dragging
signal scaleCommit()
signal scaleChange()
Node {
rotation: globalOrientation || !targetNode ? Qt.vector3d(0, 0, 0) : targetNode.sceneRotation
ScaleRod {
id: scaleRodX
objectName: "scaleRod X"
rotation: Qt.vector3d(0, 0, -90)
targetNode: scaleGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1))
: Qt.rgba(1, 0, 0, 1)
view3D: scaleGizmo.view3D
active: scaleGizmo.visible
onScaleCommit: scaleGizmo.scaleCommit()
onScaleChange: scaleGizmo.scaleChange()
}
ScaleRod {
id: scaleRodY
objectName: "scaleRod Y"
rotation: Qt.vector3d(0, 0, 0)
targetNode: scaleGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1))
: Qt.rgba(0, 0.6, 0, 1)
view3D: scaleGizmo.view3D
active: scaleGizmo.visible
onScaleCommit: scaleGizmo.scaleCommit()
onScaleChange: scaleGizmo.scaleChange()
}
ScaleRod {
id: scaleRodZ
objectName: "scaleRod Z"
rotation: Qt.vector3d(90, 0, 0)
targetNode: scaleGizmo.targetNode
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1))
: Qt.rgba(0, 0, 1, 1)
view3D: scaleGizmo.view3D
active: scaleGizmo.visible
onScaleCommit: scaleGizmo.scaleCommit()
onScaleChange: scaleGizmo.scaleChange()
}
}
Model {
id: centerCube
source: "#Cube"
scale: Qt.vector3d(0.024, 0.024, 0.024)
materials: DefaultMaterial {
id: material
emissiveColor: highlightOnHover
&& (centerMouseArea.hovering || centerMouseArea.dragging)
? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1))
: Qt.rgba(0.5, 0.5, 0.5, 1)
lighting: DefaultMaterial.NoLighting
}
MouseArea3D {
id: centerMouseArea
view3D: scaleGizmo.view3D
x: -60
y: -60
width: 120
height: 120
rotation: view3D.camera.rotation
grabsMouse: scaleGizmo.targetNode
priority: 1
active: scaleGizmo.visible
property var _startScale
property var _startScreenPos
function localScale(screenPos)
{
var yDelta = screenPos.y - _startScreenPos.y;
if (yDelta === 0)
return;
var scaler = 1.0 + (yDelta * 0.025);
if (scaler === 0 )
scaler = 0.0001;
if (scaler < 0)
scaler = -scaler;
return Qt.vector3d(scaler * _startScale.x,
scaler * _startScale.y,
scaler * _startScale.z);
}
onPressed: {
if (!scaleGizmo.targetNode)
return;
// Recreate vector so we don't follow the changes in targetNode.scale
_startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x,
scaleGizmo.targetNode.scale.y,
scaleGizmo.targetNode.scale.z);
_startScreenPos = screenPos;
}
onDragged: {
if (!scaleGizmo.targetNode)
return;
scaleGizmo.targetNode.scale = localScale(screenPos);
scaleGizmo.scaleChange();
}
onReleased: {
if (!scaleGizmo.targetNode)
return;
scaleGizmo.targetNode.scale = localScale(screenPos);
scaleGizmo.scaleCommit();
}
}
}
}

View File

@@ -0,0 +1,72 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.0
import QtQuick3D 1.0
import MouseArea3D 1.0
DirectionalDraggable {
id: scaleRod
source: "meshes/scalerod.mesh"
signal scaleCommit()
signal scaleChange()
property var _startScale
Model {
source: "#Cube"
y: 10
scale: Qt.vector3d(0.025, 0.025, 0.025)
materials: DefaultMaterial {
id: material
emissiveColor: scaleRod.color
lighting: DefaultMaterial.NoLighting
}
}
function localScale(mouseArea, sceneRelativeDistance)
{
return mouseArea.getNewScale(targetNode, _startScale, _pointerPosPressed,
sceneRelativeDistance, sceneScale.x);
}
onPressed: {
// Recreate vector so we don't follow the changes in targetNode.sceneScale
_startScale = Qt.vector3d(targetNode.sceneScale.x,
targetNode.sceneScale.y,
targetNode.sceneScale.z);
}
onDragged: {
targetNode.scale = localScale(mouseArea, sceneRelativeDistance);
scaleChange();
}
onReleased: {
targetNode.scale = localScale(mouseArea, sceneRelativeDistance);
scaleCommit();
}
}

View File

@@ -60,6 +60,11 @@ bool MouseArea3D::grabsMouse() const
return m_grabsMouse; return m_grabsMouse;
} }
bool MouseArea3D::active() const
{
return m_active;
}
qreal MouseArea3D::x() const qreal MouseArea3D::x() const
{ {
return m_x; return m_x;
@@ -103,6 +108,15 @@ void MouseArea3D::setGrabsMouse(bool grabsMouse)
emit grabsMouseChanged(grabsMouse); emit grabsMouseChanged(grabsMouse);
} }
void MouseArea3D::setActive(bool active)
{
if (m_active == active)
return;
m_active = active;
emit activeChanged(active);
}
void MouseArea3D::setX(qreal x) void MouseArea3D::setX(qreal x)
{ {
if (qFuzzyCompare(m_x, x)) if (qFuzzyCompare(m_x, x))
@@ -190,6 +204,73 @@ QVector3D MouseArea3D::rayIntersectsPlane(const QVector3D &rayPos0,
return rayPos0 + distanceFromRayPos0ToPlane * rayDirection; return rayPos0 + distanceFromRayPos0ToPlane * rayDirection;
} }
// Get a new scale based on a relative scene distance along an axis (used to adjust scale via drag)
// This function never returns a negative scaling
QVector3D MouseArea3D::getNewScale(QQuick3DNode *node, const QVector3D &startScale,
const QVector3D &pressPos,
const QVector3D &sceneRelativeDistance, float scaler)
{
if (node) {
// Note: This only returns correct scale when scale is positive
auto getScale = [&](const QMatrix4x4 &m) -> QVector3D {
return QVector3D(m.column(0).length(), m.column(1).length(), m.column(2).length());
};
const float constantDragScaler = 0.1f;
const float nonZeroValue = 0.0001f;
if (qFuzzyIsNull(scaler))
scaler = nonZeroValue;
const QVector3D scenePos = node->scenePosition();
const QMatrix4x4 parentTransform = node->parentNode()->sceneTransform();
QMatrix4x4 newTransform = node->sceneTransform();
QVector3D normalRelDist = sceneRelativeDistance.normalized();
float direction = QVector3D::dotProduct((pressPos - scenePos).normalized(), normalRelDist);
float magnitude = constantDragScaler * sceneRelativeDistance.length() / scaler;
// Reset everything but rotation to ensure translation and scale do not affect rotate below
newTransform(0,3) = 0;
newTransform(1,3) = 0;
newTransform(2,3) = 0;
QVector3D curScale = getScale(newTransform);
if (qFuzzyIsNull(curScale.x()))
curScale.setX(nonZeroValue);
if (qFuzzyIsNull(curScale.y()))
curScale.setY(nonZeroValue);
if (qFuzzyIsNull(curScale.z()))
curScale.setZ(nonZeroValue);
newTransform.scale({1.f / curScale.x(), 1.f / curScale.y(), 1.f / curScale.z()});
// Rotate relative distance according to object rotation
normalRelDist = newTransform.inverted().map(normalRelDist).normalized();
// Ensure scaling is always positive/negative according to direction
normalRelDist.setX(qAbs(normalRelDist.x()));
normalRelDist.setY(qAbs(normalRelDist.y()));
normalRelDist.setZ(qAbs(normalRelDist.z()));
QVector3D scaleVec = normalRelDist;
scaleVec *= magnitude;
if (direction > 0) {
scaleVec.setX(scaleVec.x() + 1.f);
scaleVec.setY(scaleVec.y() + 1.f);
scaleVec.setZ(scaleVec.z() + 1.f);
} else {
scaleVec.setX(1.f - scaleVec.x());
scaleVec.setY(1.f - scaleVec.y());
scaleVec.setZ(1.f - scaleVec.z());
}
scaleVec *= startScale;
newTransform = parentTransform;
newTransform.scale(scaleVec);
const QMatrix4x4 localTransform = parentTransform.inverted() * newTransform;
return getScale(localTransform);
}
return startScale;
}
QVector3D MouseArea3D::getMousePosInPlane(const QPointF &mousePosInView) const QVector3D MouseArea3D::getMousePosInPlane(const QPointF &mousePosInView) const
{ {
const QVector3D mousePos1(float(mousePosInView.x()), float(mousePosInView.y()), 0); const QVector3D mousePos1(float(mousePosInView.x()), float(mousePosInView.y()), 0);
@@ -207,8 +288,8 @@ QVector3D MouseArea3D::getMousePosInPlane(const QPointF &mousePosInView) const
bool MouseArea3D::eventFilter(QObject *, QEvent *event) bool MouseArea3D::eventFilter(QObject *, QEvent *event)
{ {
if (m_grabsMouse && s_mouseGrab && s_mouseGrab != this if (!m_active || (m_grabsMouse && s_mouseGrab && s_mouseGrab != this
&& (m_priority <= s_mouseGrab->m_priority || s_mouseGrab->m_dragging)) { && (m_priority <= s_mouseGrab->m_priority || s_mouseGrab->m_dragging))) {
return false; return false;
} }
@@ -227,7 +308,7 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event)
m_mousePosInPlane = getMousePosInPlane(mouseEvent->pos()); m_mousePosInPlane = getMousePosInPlane(mouseEvent->pos());
if (mouseOnTopOfMouseArea(m_mousePosInPlane)) { if (mouseOnTopOfMouseArea(m_mousePosInPlane)) {
setDragging(true); setDragging(true);
emit pressed(m_mousePosInPlane); emit pressed(m_mousePosInPlane, mouseEvent->globalPos());
if (m_grabsMouse) { if (m_grabsMouse) {
if (s_mouseGrab && s_mouseGrab != this) { if (s_mouseGrab && s_mouseGrab != this) {
s_mouseGrab->setDragging(false); s_mouseGrab->setDragging(false);
@@ -250,7 +331,7 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event)
if (qFuzzyCompare(mousePosInPlane.z(), -1)) if (qFuzzyCompare(mousePosInPlane.z(), -1))
mousePosInPlane = m_mousePosInPlane; mousePosInPlane = m_mousePosInPlane;
setDragging(false); setDragging(false);
emit released(mousePosInPlane); emit released(mousePosInPlane, mouseEvent->globalPos());
if (m_grabsMouse) { if (m_grabsMouse) {
if (s_mouseGrab && s_mouseGrab != this) { if (s_mouseGrab && s_mouseGrab != this) {
s_mouseGrab->setDragging(false); s_mouseGrab->setDragging(false);
@@ -290,7 +371,7 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event)
if (m_dragging && !qFuzzyCompare(mousePosInPlane.z(), -1)) { if (m_dragging && !qFuzzyCompare(mousePosInPlane.z(), -1)) {
m_mousePosInPlane = mousePosInPlane; m_mousePosInPlane = mousePosInPlane;
emit dragged(mousePosInPlane); emit dragged(mousePosInPlane, mouseEvent->globalPos());
} }
break; break;

View File

@@ -49,6 +49,7 @@ class MouseArea3D : public QQuick3DNode
Q_PROPERTY(bool hovering READ hovering NOTIFY hoveringChanged) Q_PROPERTY(bool hovering READ hovering NOTIFY hoveringChanged)
Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged) Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged)
Q_PROPERTY(int active READ active WRITE setActive NOTIFY activeChanged)
Q_INTERFACES(QQmlParserStatus) Q_INTERFACES(QQmlParserStatus)
@@ -66,10 +67,12 @@ public:
bool hovering() const; bool hovering() const;
bool dragging() const; bool dragging() const;
bool grabsMouse() const; bool grabsMouse() const;
bool active() const;
public slots: public slots:
void setView3D(QQuick3DViewport *view3D); void setView3D(QQuick3DViewport *view3D);
void setGrabsMouse(bool grabsMouse); void setGrabsMouse(bool grabsMouse);
void setActive(bool active);
void setX(qreal x); void setX(qreal x);
void setY(qreal y); void setY(qreal y);
@@ -82,6 +85,10 @@ public slots:
const QVector3D &planePos, const QVector3D &planePos,
const QVector3D &planeNormal) const; const QVector3D &planeNormal) const;
Q_INVOKABLE QVector3D getNewScale(QQuick3DNode *node, const QVector3D &startScale,
const QVector3D &pressPos,
const QVector3D &sceneRelativeDistance, float scaler);
signals: signals:
void view3DChanged(); void view3DChanged();
@@ -93,9 +100,10 @@ signals:
void hoveringChanged(); void hoveringChanged();
void draggingChanged(); void draggingChanged();
void pressed(const QVector3D &pointerPosition); void activeChanged(bool active);
void released(const QVector3D &pointerPosition); void pressed(const QVector3D &scenePos, const QPoint &screenPos);
void dragged(const QVector3D &pointerPosition); void released(const QVector3D &scenePos, const QPoint &screenPos);
void dragged(const QVector3D &scenePos, const QPoint &screenPos);
void grabsMouseChanged(bool grabsMouse); void grabsMouseChanged(bool grabsMouse);
protected: protected:
@@ -118,6 +126,7 @@ private:
bool m_hovering = false; bool m_hovering = false;
bool m_dragging = false; bool m_dragging = false;
bool m_active = false;
QVector3D getMousePosInPlane(const QPointF &mousePosInView) const; QVector3D getMousePosInPlane(const QPointF &mousePosInView) const;

View File

@@ -97,12 +97,12 @@ QObject *Qt5InformationNodeInstanceServer::createEditView3D(QQmlEngine *engine)
} }
QObject::connect(window, SIGNAL(objectClicked(QVariant)), this, SLOT(objectClicked(QVariant))); QObject::connect(window, SIGNAL(objectClicked(QVariant)), this, SLOT(objectClicked(QVariant)));
QObject::connect(window, SIGNAL(commitObjectPosition(QVariant)), QObject::connect(window, SIGNAL(commitObjectProperty(QVariant, QVariant)),
this, SLOT(handleObjectPositionCommit(QVariant))); this, SLOT(handleObjectPropertyCommit(QVariant, QVariant)));
QObject::connect(window, SIGNAL(moveObjectPosition(QVariant)), QObject::connect(window, SIGNAL(changeObjectProperty(QVariant, QVariant)),
this, SLOT(handleObjectPositionMove(QVariant))); this, SLOT(handleObjectPropertyChange(QVariant, QVariant)));
QObject::connect(&m_moveTimer, &QTimer::timeout, QObject::connect(&m_propertyChangeTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::handleObjectPositionMoveTimeout); this, &Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout);
//For macOS we have to use the 4.1 core profile //For macOS we have to use the 4.1 core profile
QSurfaceFormat surfaceFormat = window->requestedFormat(); QSurfaceFormat surfaceFormat = window->requestedFormat();
@@ -188,28 +188,36 @@ void Qt5InformationNodeInstanceServer::modifyVariantValue(
} }
} }
void Qt5InformationNodeInstanceServer::handleObjectPositionCommit(const QVariant &object) void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &object,
const QVariant &propName)
{ {
modifyVariantValue(object, "position", ValuesModifiedCommand::TransactionOption::End); modifyVariantValue(object, propName.toByteArray(),
m_movedNode = {}; ValuesModifiedCommand::TransactionOption::End);
m_moveTimer.stop(); m_changedNode = {};
m_changedProperty = {};
m_propertyChangeTimer.stop();
} }
void Qt5InformationNodeInstanceServer::handleObjectPositionMove(const QVariant &object) void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &object,
const QVariant &propName)
{ {
if (m_movedNode.isNull()) { PropertyName propertyName(propName.toByteArray());
modifyVariantValue(object, "position", ValuesModifiedCommand::TransactionOption::Start); if (m_changedProperty != propertyName || m_changedNode != object) {
} else { if (!m_changedNode.isNull())
if (!m_moveTimer.isActive()) handleObjectPropertyCommit(m_changedNode, m_changedProperty);
m_moveTimer.start(); modifyVariantValue(object, propertyName,
ValuesModifiedCommand::TransactionOption::Start);
} else if (!m_propertyChangeTimer.isActive()) {
m_propertyChangeTimer.start();
} }
m_movedNode = object; m_changedNode = object;
m_changedProperty = propertyName;
} }
Qt5InformationNodeInstanceServer::Qt5InformationNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) : Qt5InformationNodeInstanceServer::Qt5InformationNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) :
Qt5NodeInstanceServer(nodeInstanceClient) Qt5NodeInstanceServer(nodeInstanceClient)
{ {
m_moveTimer.setInterval(100); m_propertyChangeTimer.setInterval(100);
} }
void Qt5InformationNodeInstanceServer::sendTokenBack() void Qt5InformationNodeInstanceServer::sendTokenBack()
@@ -282,9 +290,10 @@ void Qt5InformationNodeInstanceServer::modifyProperties(
nodeInstanceClient()->valuesModified(createValuesModifiedCommand(properties)); nodeInstanceClient()->valuesModified(createValuesModifiedCommand(properties));
} }
void Qt5InformationNodeInstanceServer::handleObjectPositionMoveTimeout() void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout()
{ {
modifyVariantValue(m_movedNode, "position", ValuesModifiedCommand::TransactionOption::None); modifyVariantValue(m_changedNode, m_changedProperty,
ValuesModifiedCommand::TransactionOption::None);
} }
QObject *Qt5InformationNodeInstanceServer::findRootNodeOf3DViewport( QObject *Qt5InformationNodeInstanceServer::findRootNodeOf3DViewport(

View File

@@ -51,8 +51,8 @@ public:
private slots: private slots:
void objectClicked(const QVariant &object); void objectClicked(const QVariant &object);
void handleObjectPositionCommit(const QVariant &object); void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName);
void handleObjectPositionMove(const QVariant &object); void handleObjectPropertyChange(const QVariant &object, const QVariant &propName);
protected: protected:
void collectItemChangesAndSendChangeCommands() override; void collectItemChangesAndSendChangeCommands() override;
@@ -64,7 +64,7 @@ protected:
void modifyProperties(const QVector<InstancePropertyValueTriple> &properties); void modifyProperties(const QVector<InstancePropertyValueTriple> &properties);
private: private:
void handleObjectPositionMoveTimeout(); void handleObjectPropertyChangeTimeout();
QObject *createEditView3D(QQmlEngine *engine); QObject *createEditView3D(QQmlEngine *engine);
void setup3DEditView(const QList<ServerNodeInstance> &instanceList); void setup3DEditView(const QList<ServerNodeInstance> &instanceList);
QObject *findRootNodeOf3DViewport(const QList<ServerNodeInstance> &instanceList) const; QObject *findRootNodeOf3DViewport(const QList<ServerNodeInstance> &instanceList) const;
@@ -81,8 +81,9 @@ private:
QSet<ServerNodeInstance> m_parentChangedSet; QSet<ServerNodeInstance> m_parentChangedSet;
QList<ServerNodeInstance> m_completedComponentList; QList<ServerNodeInstance> m_completedComponentList;
QList<TokenCommand> m_tokenList; QList<TokenCommand> m_tokenList;
QTimer m_moveTimer; QTimer m_propertyChangeTimer;
QVariant m_movedNode; QVariant m_changedNode;
PropertyName m_changedProperty;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -15,7 +15,11 @@
<file>mockfiles/LightGizmo.qml</file> <file>mockfiles/LightGizmo.qml</file>
<file>mockfiles/IconGizmo.qml</file> <file>mockfiles/IconGizmo.qml</file>
<file>mockfiles/Overlay2D.qml</file> <file>mockfiles/Overlay2D.qml</file>
<file>mockfiles/DirectionalDraggable.qml</file>
<file>mockfiles/ScaleRod.qml</file>
<file>mockfiles/ScaleGizmo.qml</file>
<file>mockfiles/meshes/arrow.mesh</file> <file>mockfiles/meshes/arrow.mesh</file>
<file>mockfiles/meshes/scalerod.mesh</file>
<file>mockfiles/images/camera-pick-icon.png</file> <file>mockfiles/images/camera-pick-icon.png</file>
<file>mockfiles/images/camera-pick-icon@2x.png</file> <file>mockfiles/images/camera-pick-icon@2x.png</file>
<file>mockfiles/images/light-pick-icon.png</file> <file>mockfiles/images/light-pick-icon.png</file>