QmlDesigner: MoveGizmo improvements in 3D edit view

Mouse press and release are now properly handled by MouseArea3D.
This fixes various issues:

- No need to move the mouse after release for release to register.
- Drag is no longer limited to the 3D edit window, though it is still
  limited to the screen.
- Drag arrows no longer register start of drag if you click outside
  the arrow and then move the cursor over the arrow while holding the
  mouse button down.

Also added the missing center ball to the MoveGizmo to allow free
dragging along the camera plane.

Change-Id: Iab55ae79f8af024534510e5fd29379532ac74025
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2019-10-30 10:52:51 +02:00
parent d4797587b1
commit 97788c457e
5 changed files with 235 additions and 80 deletions

View File

@@ -35,7 +35,7 @@ Model {
property View3D view3D property View3D view3D
property alias color: material.emissiveColor property alias color: material.emissiveColor
property Node targetNode: null property Node targetNode: null
property bool isDragging: false property bool dragging: false
readonly property bool hovering: mouseAreaYZ.hovering || mouseAreaXZ.hovering readonly property bool hovering: mouseAreaYZ.hovering || mouseAreaXZ.hovering
@@ -47,7 +47,7 @@ Model {
materials: DefaultMaterial { materials: DefaultMaterial {
id: material id: material
emissiveColor: mouseAreaFront.hovering ? "white" : Qt.rgba(1.0, 0.0, 0.0, 1.0) emissiveColor: "white"
lighting: DefaultMaterial.NoLighting lighting: DefaultMaterial.NoLighting
} }
@@ -60,7 +60,7 @@ Model {
_pointerPosPressed = mouseArea.mapPositionToScene(maskedPosition); _pointerPosPressed = mouseArea.mapPositionToScene(maskedPosition);
var sp = targetNode.scenePosition; var sp = targetNode.scenePosition;
_targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z); _targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z);
isDragging = true; dragging = true;
} }
function posInParent(mouseArea, pointerPosition) function posInParent(mouseArea, pointerPosition)
@@ -95,7 +95,7 @@ Model {
return; return;
targetNode.position = posInParent(mouseArea, pointerPosition); targetNode.position = posInParent(mouseArea, pointerPosition);
isDragging = false; dragging = false;
arrow.positionCommit(); arrow.positionCommit();
} }

View File

@@ -112,11 +112,7 @@ Window {
targetNode: viewWindow.selectedNode targetNode: viewWindow.selectedNode
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
rotation: globalControl.checked || !viewWindow.selectedNode
? Qt.vector3d(0, 0, 0)
: viewWindow.selectedNode.sceneRotation
visible: selectedNode visible: selectedNode
view3D: overlayView view3D: overlayView
@@ -194,7 +190,7 @@ Window {
targetView: overlayView targetView: overlayView
offsetX: 0 offsetX: 0
offsetY: 45 offsetY: 45
visible: moveGizmo.isDragging visible: moveGizmo.dragging
Rectangle { Rectangle {
color: "white" color: "white"

View File

@@ -25,56 +25,131 @@
import QtQuick 2.0 import QtQuick 2.0
import QtQuick3D 1.0 import QtQuick3D 1.0
import MouseArea3D 1.0
Node { Node {
id: arrows id: moveGizmo
property View3D view3D property View3D view3D
property bool highlightOnHover: false property bool highlightOnHover: false
property Node targetNode: null property Node targetNode: null
readonly property bool isDragging: arrowX.isDragging || arrowY.isDragging || arrowZ.isDragging property bool globalOrientation: true
readonly property bool dragging: arrowX.dragging || arrowY.dragging || arrowZ.dragging
scale: Qt.vector3d(5, 5, 5) || centerMouseArea.dragging
signal positionCommit() signal positionCommit()
signal positionMove() signal positionMove()
Node {
rotation: globalOrientation || !targetNode ? Qt.vector3d(0, 0, 0) : targetNode.sceneRotation
Arrow { Arrow {
id: arrowX id: arrowX
objectName: "Arrow X" objectName: "Arrow X"
rotation: Qt.vector3d(0, 0, -90) rotation: Qt.vector3d(0, 0, -90)
targetNode: arrows.targetNode targetNode: moveGizmo.targetNode
color: highlightOnHover && hovering ? 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: arrows.view3D view3D: moveGizmo.view3D
onPositionCommit: arrows.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: arrows.positionMove() onPositionMove: moveGizmo.positionMove()
} }
Arrow { Arrow {
id: arrowY id: arrowY
objectName: "Arrow Y" objectName: "Arrow Y"
rotation: Qt.vector3d(0, 0, 0) rotation: Qt.vector3d(0, 0, 0)
targetNode: arrows.targetNode targetNode: moveGizmo.targetNode
color: highlightOnHover && hovering ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1))
: Qt.rgba(0, 0, 1, 1) : Qt.rgba(0, 0, 1, 1)
view3D: arrows.view3D view3D: moveGizmo.view3D
onPositionCommit: arrows.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: arrows.positionMove() onPositionMove: moveGizmo.positionMove()
} }
Arrow { Arrow {
id: arrowZ id: arrowZ
objectName: "Arrow Z" objectName: "Arrow Z"
rotation: Qt.vector3d(90, 0, 0) rotation: Qt.vector3d(90, 0, 0)
targetNode: arrows.targetNode targetNode: moveGizmo.targetNode
color: highlightOnHover && hovering ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1))
: Qt.rgba(0, 0.6, 0, 1) : Qt.rgba(0, 0.6, 0, 1)
view3D: arrows.view3D view3D: moveGizmo.view3D
onPositionCommit: arrows.positionCommit() onPositionCommit: moveGizmo.positionCommit()
onPositionMove: arrows.positionMove() onPositionMove: moveGizmo.positionMove()
}
}
Model {
id: centerBall
source: "#Sphere"
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: moveGizmo.view3D
x: -60
y: -60
width: 120
height: 120
rotation: view3D.camera.rotation
grabsMouse: moveGizmo.targetNode
priority: 1
property var _pointerPosPressed
property var _targetStartPos
function posInParent(pointerPosition)
{
var scenePointerPos = mapPositionToScene(pointerPosition);
var sceneRelativeDistance = Qt.vector3d(
scenePointerPos.x - _pointerPosPressed.x,
scenePointerPos.y - _pointerPosPressed.y,
scenePointerPos.z - _pointerPosPressed.z);
var newScenePos = Qt.vector3d(
_targetStartPos.x + sceneRelativeDistance.x,
_targetStartPos.y + sceneRelativeDistance.y,
_targetStartPos.z + sceneRelativeDistance.z);
return moveGizmo.targetNode.parent.mapPositionFromScene(newScenePos);
}
onPressed: {
if (!moveGizmo.targetNode)
return;
_pointerPosPressed = mapPositionToScene(pointerPosition);
var sp = moveGizmo.targetNode.scenePosition;
_targetStartPos = Qt.vector3d(sp.x, sp.y, sp.z);
}
onDragged: {
if (!moveGizmo.targetNode)
return;
moveGizmo.targetNode.position = posInParent(pointerPosition);
moveGizmo.positionMove();
}
onReleased: {
if (!moveGizmo.targetNode)
return;
moveGizmo.targetNode.position = posInParent(pointerPosition);
moveGizmo.positionCommit();
}
}
} }
} }

View File

@@ -80,6 +80,11 @@ qreal MouseArea3D::height() const
return m_height; return m_height;
} }
int MouseArea3D::priority() const
{
return m_priority;
}
void MouseArea3D::setView3D(QQuick3DViewport *view3D) void MouseArea3D::setView3D(QQuick3DViewport *view3D)
{ {
if (m_view3D == view3D) if (m_view3D == view3D)
@@ -134,6 +139,15 @@ void MouseArea3D::setHeight(qreal height)
emit heightChanged(height); emit heightChanged(height);
} }
void MouseArea3D::setPriority(int level)
{
if (m_priority == level)
return;
m_priority = level;
emit priorityChanged(level);
}
void MouseArea3D::componentComplete() void MouseArea3D::componentComplete()
{ {
if (!m_view3D) { if (!m_view3D) {
@@ -193,51 +207,94 @@ QVector3D MouseArea3D::getMousePosInPlane(const QPointF &mousePosInView) const
bool MouseArea3D::eventFilter(QObject *, QEvent *event) bool MouseArea3D::eventFilter(QObject *, QEvent *event)
{ {
switch (event->type()) { if (m_grabsMouse && s_mouseGrab && s_mouseGrab != this
case QEvent::HoverMove: { && (m_priority <= s_mouseGrab->m_priority || s_mouseGrab->m_dragging)) {
if (m_grabsMouse && s_mouseGrab && s_mouseGrab != this) return false;
break; }
auto mouseOnTopOfMouseArea = [this](const QVector3D &mousePosInPlane) -> bool {
return !qFuzzyCompare(mousePosInPlane.z(), -1)
&& mousePosInPlane.x() >= float(m_x)
&& mousePosInPlane.x() <= float(m_x + m_width)
&& mousePosInPlane.y() >= float(m_y)
&& mousePosInPlane.y() <= float(m_y + m_height);
};
switch (event->type()) {
case QEvent::MouseButtonPress: {
auto const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_mousePosInPlane = getMousePosInPlane(mouseEvent->pos());
if (mouseOnTopOfMouseArea(m_mousePosInPlane)) {
setDragging(true);
emit pressed(m_mousePosInPlane);
if (m_grabsMouse) {
if (s_mouseGrab && s_mouseGrab != this) {
s_mouseGrab->setDragging(false);
s_mouseGrab->setHovering(false);
}
s_mouseGrab = this;
setHovering(true);
}
event->accept();
return true;
}
}
break;
}
case QEvent::MouseButtonRelease: {
auto const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton) {
if (m_dragging) {
QVector3D mousePosInPlane = getMousePosInPlane(mouseEvent->pos());
if (qFuzzyCompare(mousePosInPlane.z(), -1))
mousePosInPlane = m_mousePosInPlane;
setDragging(false);
emit released(mousePosInPlane);
if (m_grabsMouse) {
if (s_mouseGrab && s_mouseGrab != this) {
s_mouseGrab->setDragging(false);
s_mouseGrab->setHovering(false);
}
if (mouseOnTopOfMouseArea(mousePosInPlane)) {
s_mouseGrab = this;
setHovering(true);
} else {
s_mouseGrab = nullptr;
setHovering(false);
}
}
event->accept();
return true;
}
}
break;
}
case QEvent::MouseMove:
case QEvent::HoverMove: {
auto const mouseEvent = static_cast<QMouseEvent *>(event); auto const mouseEvent = static_cast<QMouseEvent *>(event);
const QVector3D mousePosInPlane = getMousePosInPlane(mouseEvent->pos()); const QVector3D mousePosInPlane = getMousePosInPlane(mouseEvent->pos());
if (qFuzzyCompare(mousePosInPlane.z(), -1)) const bool hasMouse = mouseOnTopOfMouseArea(mousePosInPlane);
break;
const bool mouseOnTopOfMouseArea = setHovering(hasMouse);
mousePosInPlane.x() >= float(m_x) &&
mousePosInPlane.x() <= float(m_x + m_width) &&
mousePosInPlane.y() >= float(m_y) &&
mousePosInPlane.y() <= float(m_y + m_height);
const bool buttonPressed = QGuiApplication::mouseButtons().testFlag(Qt::LeftButton); if (m_grabsMouse) {
if (m_hovering && s_mouseGrab && s_mouseGrab != this)
s_mouseGrab->setHovering(false);
// The filter will detect a mouse press on the view, but not a mouse release, since the if (m_hovering || m_dragging)
// former is not accepted by the view, which means that the release will end up being s_mouseGrab = this;
// sent elsewhere. So we need this extra logic inside HoverMove, rather than in else if (s_mouseGrab == this)
// MouseButtonRelease, which would otherwise be more elegant. s_mouseGrab = nullptr;
if (m_hovering != mouseOnTopOfMouseArea) {
m_hovering = mouseOnTopOfMouseArea;
emit hoveringChanged();
} }
if (!m_dragging && m_hovering && buttonPressed) { if (m_dragging && !qFuzzyCompare(mousePosInPlane.z(), -1)) {
m_dragging = true; m_mousePosInPlane = mousePosInPlane;
emit pressed(mousePosInPlane);
emit draggingChanged();
} else if (m_dragging && !buttonPressed) {
m_dragging = false;
emit released(mousePosInPlane);
emit draggingChanged();
}
if (m_grabsMouse)
s_mouseGrab = m_hovering || m_dragging ? this : nullptr;
if (m_dragging)
emit dragged(mousePosInPlane); emit dragged(mousePosInPlane);
}
break; } break;
}
default: default:
break; break;
} }
@@ -245,6 +302,24 @@ bool MouseArea3D::eventFilter(QObject *, QEvent *event)
return false; return false;
} }
void MouseArea3D::setDragging(bool enable)
{
if (m_dragging == enable)
return;
m_dragging = enable;
emit draggingChanged();
}
void MouseArea3D::setHovering(bool enable)
{
if (m_hovering == enable)
return;
m_hovering = enable;
emit hoveringChanged();
}
} }
} }

View File

@@ -48,6 +48,7 @@ class MouseArea3D : public QQuick3DNode
Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged)
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_INTERFACES(QQmlParserStatus) Q_INTERFACES(QQmlParserStatus)
@@ -60,6 +61,7 @@ public:
qreal y() const; qreal y() const;
qreal width() const; qreal width() const;
qreal height() const; qreal height() const;
int priority() const;
bool hovering() const; bool hovering() const;
bool dragging() const; bool dragging() const;
@@ -73,6 +75,7 @@ public slots:
void setY(qreal y); void setY(qreal y);
void setWidth(qreal width); void setWidth(qreal width);
void setHeight(qreal height); void setHeight(qreal height);
void setPriority(int level);
Q_INVOKABLE QVector3D rayIntersectsPlane(const QVector3D &rayPos0, Q_INVOKABLE QVector3D rayIntersectsPlane(const QVector3D &rayPos0,
const QVector3D &rayPos1, const QVector3D &rayPos1,
@@ -86,6 +89,7 @@ signals:
void yChanged(qreal y); void yChanged(qreal y);
void widthChanged(qreal width); void widthChanged(qreal width);
void heightChanged(qreal height); void heightChanged(qreal height);
void priorityChanged(int level);
void hoveringChanged(); void hoveringChanged();
void draggingChanged(); void draggingChanged();
@@ -100,6 +104,9 @@ protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
private: private:
void setDragging(bool enable);
void setHovering(bool enable);
Q_DISABLE_COPY(MouseArea3D) Q_DISABLE_COPY(MouseArea3D)
QQuick3DViewport *m_view3D = nullptr; QQuick3DViewport *m_view3D = nullptr;
@@ -107,6 +114,7 @@ private:
qreal m_y; qreal m_y;
qreal m_width; qreal m_width;
qreal m_height; qreal m_height;
int m_priority = 0;
bool m_hovering = false; bool m_hovering = false;
bool m_dragging = false; bool m_dragging = false;
@@ -115,6 +123,7 @@ private:
static MouseArea3D *s_mouseGrab; static MouseArea3D *s_mouseGrab;
bool m_grabsMouse; bool m_grabsMouse;
QVector3D m_mousePosInPlane;
}; };
} }