QmlDesigner: Initialize 3D edit view tool state at initial show

Whenever the tool state of 3D edit view changes, creator is notified
to store the state. The stored state is then restored to 3D edit view
when it is restarted. The state persists only for the lifetime of the
creator.

Change-Id: I888b3ba82693a17a2f163924fbae1cfa27593890
Fixes: QDS-1351
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2020-01-16 17:42:04 +02:00
parent 57c41edf54
commit ef95674378
13 changed files with 199 additions and 28 deletions

View File

@@ -39,7 +39,8 @@ CreateSceneCommand::CreateSceneCommand(const QVector<InstanceContainer> &instanc
const QVector<PropertyValueContainer> &auxiliaryChangeVector,
const QVector<AddImportContainer> &importVector,
const QVector<MockupTypeContainer> &mockupTypeVector,
const QUrl &fileUrl)
const QUrl &fileUrl,
const QVariantMap &edit3dToolStates)
: m_instanceVector(instanceContainer),
m_reparentInstanceVector(reparentContainer),
m_idVector(idVector),
@@ -48,7 +49,8 @@ CreateSceneCommand::CreateSceneCommand(const QVector<InstanceContainer> &instanc
m_auxiliaryChangeVector(auxiliaryChangeVector),
m_importVector(importVector),
m_mockupTypeVector(mockupTypeVector),
m_fileUrl(fileUrl)
m_fileUrl(fileUrl),
m_edit3dToolStates(edit3dToolStates)
{
}
@@ -97,6 +99,11 @@ QUrl CreateSceneCommand::fileUrl() const
return m_fileUrl;
}
QVariantMap CreateSceneCommand::edit3dToolStates() const
{
return m_edit3dToolStates;
}
QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command)
{
out << command.instances();
@@ -108,6 +115,7 @@ QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command)
out << command.imports();
out << command.mockupTypes();
out << command.fileUrl();
out << command.edit3dToolStates();
return out;
}
@@ -123,6 +131,7 @@ QDataStream &operator>>(QDataStream &in, CreateSceneCommand &command)
in >> command.m_importVector;
in >> command.m_mockupTypeVector;
in >> command.m_fileUrl;
in >> command.m_edit3dToolStates;
return in;
}
@@ -138,7 +147,8 @@ QDebug operator <<(QDebug debug, const CreateSceneCommand &command)
<< "auxiliaryChanges: " << command.auxiliaryChanges() << ", "
<< "imports: " << command.imports() << ", "
<< "mockupTypes: " << command.mockupTypes() << ", "
<< "fileUrl: " << command.fileUrl() << ")";
<< "fileUrl: " << command.fileUrl() << ", "
<< "edit3dToolStates: " << command.edit3dToolStates() << ")";
}
}

View File

@@ -53,7 +53,8 @@ public:
const QVector<PropertyValueContainer> &auxiliaryChangeVector,
const QVector<AddImportContainer> &importVector,
const QVector<MockupTypeContainer> &mockupTypeVector,
const QUrl &fileUrl);
const QUrl &fileUrl,
const QVariantMap &edit3dToolStates);
QVector<InstanceContainer> instances() const;
QVector<ReparentContainer> reparentInstances() const;
@@ -64,6 +65,7 @@ public:
QVector<AddImportContainer> imports() const;
QVector<MockupTypeContainer> mockupTypes() const;
QUrl fileUrl() const;
QVariantMap edit3dToolStates() const;
private:
QVector<InstanceContainer> m_instanceVector;
@@ -75,6 +77,7 @@ private:
QVector<AddImportContainer> m_importVector;
QVector<MockupTypeContainer> m_mockupTypeVector;
QUrl m_fileUrl;
QVariantMap m_edit3dToolStates;
};
QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command);

View File

@@ -34,7 +34,7 @@ namespace QmlDesigner {
class PuppetToCreatorCommand
{
public:
enum Type { Key_Pressed, None };
enum Type { KeyPressed, Edit3DToolState, None };
PuppetToCreatorCommand(Type type, const QVariant &data);
PuppetToCreatorCommand() = default;

View File

@@ -216,6 +216,9 @@ void NodeInstanceServerInterface::registerCommands()
qRegisterMetaType<QPair<int, int>>("QPairIntInt");
qRegisterMetaTypeStreamOperators<QPair<int, int>>("QPairIntInt");
qRegisterMetaType<QPair<QString, QVariant>>("QPairStringVariant");
qRegisterMetaTypeStreamOperators<QPair<QString, QVariant>>("QPairStringVariant");
}
}

View File

@@ -45,6 +45,25 @@ Item {
property real _defaultCameraLookAtDistance: 0
property Camera _prevCamera: null
function restoreCameraState(cameraState) {
_lookAtPoint = cameraState[0];
_zoomFactor = cameraState[1];
camera.position = cameraState[2];
camera.rotation = cameraState[3];
_generalHelper.zoomCamera(camera, 0, _defaultCameraLookAtDistance, _lookAtPoint,
_zoomFactor, false);
}
function storeCameraState(delay) {
var cameraState = [];
cameraState[0] = _lookAtPoint;
cameraState[1] = _zoomFactor;
cameraState[2] = camera.position;
cameraState[3] = camera.rotation;
_generalHelper.storeToolState("editCamState", cameraState, delay);
}
function focusObject(targetObject, rotation, updateZoom)
{
camera.rotation = rotation;
@@ -52,6 +71,7 @@ Item {
camera, _defaultCameraLookAtDistance, targetObject, view3d, _zoomFactor, updateZoom);
_lookAtPoint = newLookAtAndZoom.toVector3d();
_zoomFactor = newLookAtAndZoom.w;
storeCameraState(0);
}
function zoomRelative(distance)
@@ -118,11 +138,18 @@ Item {
}
}
onReleased: cameraCtrl._dragging = false;
onCanceled: cameraCtrl._dragging = false;
function handleRelease() {
cameraCtrl._dragging = false;
cameraCtrl.storeCameraState(0);
}
onReleased: handleRelease()
onCanceled: handleRelease()
onWheel: {
// Emprically determined divisor for nice zoom
cameraCtrl.zoomRelative(wheel.angleDelta.y / -40);
cameraCtrl.storeCameraState(500);
}
}
}

View File

@@ -40,8 +40,10 @@ Window {
flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
property alias scene: editView.importScene
property alias showEditLight: btnEditViewLight.toggled
property alias usePerspective: btnPerspective.toggled
property alias globalOrientation: btnLocalGlobal.toggled
property Node selectedNode: null // This is non-null only in single selection case
property var selectedNodes: [] // All selected nodes
@@ -55,6 +57,41 @@ Window {
signal commitObjectProperty(var object, var propName)
signal changeObjectProperty(var object, var propName)
onUsePerspectiveChanged: _generalHelper.storeToolState("usePerspective", usePerspective)
onShowEditLightChanged: _generalHelper.storeToolState("showEditLight", showEditLight)
onGlobalOrientationChanged: _generalHelper.storeToolState("globalOrientation", globalOrientation)
function updateToolStates(toolStates) {
// Init the stored state so we don't unnecessarily reflect changes back to creator
_generalHelper.initToolStates(toolStates);
if ("showEditLight" in toolStates)
showEditLight = toolStates.showEditLight;
if ("usePerspective" in toolStates)
usePerspective = toolStates.usePerspective;
if ("globalOrientation" in toolStates)
globalOrientation = toolStates.globalOrientation;
var groupIndex;
var group;
var i;
if ("groupSelect" in toolStates) {
groupIndex = toolStates.groupSelect;
group = toolbarButtons.buttonGroups["groupSelect"];
for (i = 0; i < group.length; ++i)
group[i].selected = (i === groupIndex);
}
if ("groupTransform" in toolStates) {
groupIndex = toolStates.groupTransform;
group = toolbarButtons.buttonGroups["groupTransform"];
for (i = 0; i < group.length; ++i)
group[i].selected = (i === groupIndex);
}
if ("editCamState" in toolStates)
cameraControl.restoreCameraState(toolStates.editCamState);
}
function ensureSelectionBoxes(count) {
var needMore = count - selectionBoxes.length
if (needMore > 0) {
@@ -207,7 +244,7 @@ Window {
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
highlightOnHover: true
targetNode: viewWindow.selectedNode
globalOrientation: btnLocalGlobal.toggled
globalOrientation: viewWindow.globalOrientation
visible: viewWindow.selectedNode && btnMove.selected
view3D: overlayView
dragHelper: gizmoDragHelper
@@ -234,7 +271,7 @@ Window {
scale: autoScale.getScale(Qt.vector3d(7, 7, 7))
highlightOnHover: true
targetNode: viewWindow.selectedNode
globalOrientation: btnLocalGlobal.toggled
globalOrientation: viewWindow.globalOrientation
visible: viewWindow.selectedNode && btnRotate.selected
view3D: overlayView
dragHelper: gizmoDragHelper
@@ -422,16 +459,19 @@ Window {
id: toolbar
color: "#9F000000"
width: 35
height: col.height
height: toolbarButtons.height
Column {
id: col
id: toolbarButtons
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
padding: 5
property var groupSelect: [btnSelectGroup, btnSelectItem]
property var groupTransform: [btnMove, btnRotate, btnScale]
// Button groups must be defined in parent object of buttons
property var buttonGroups: {
"groupSelect": [btnSelectGroup, btnSelectItem],
"groupTransform": [btnMove, btnRotate, btnScale]
}
ToolBarButton {
id: btnSelectItem
@@ -440,7 +480,7 @@ Window {
shortcut: "Q"
currentShortcut: selected ? "" : shortcut
tool: "item_selection"
buttonsGroup: col.groupSelect
buttonGroup: "groupSelect"
}
ToolBarButton {
@@ -449,7 +489,7 @@ Window {
shortcut: "Q"
currentShortcut: btnSelectItem.currentShortcut === shortcut ? "" : shortcut
tool: "group_selection"
buttonsGroup: col.groupSelect
buttonGroup: "groupSelect"
}
Rectangle { // separator
@@ -466,7 +506,7 @@ Window {
shortcut: "W"
currentShortcut: shortcut
tool: "move"
buttonsGroup: col.groupTransform
buttonGroup: "groupTransform"
}
ToolBarButton {
@@ -475,7 +515,7 @@ Window {
shortcut: "E"
currentShortcut: shortcut
tool: "rotate"
buttonsGroup: col.groupTransform
buttonGroup: "groupTransform"
}
ToolBarButton {
@@ -484,7 +524,7 @@ Window {
shortcut: "R"
currentShortcut: shortcut
tool: "scale"
buttonsGroup: col.groupTransform
buttonGroup: "groupTransform"
}
Rectangle { // separator

View File

@@ -33,15 +33,31 @@ Rectangle {
property string shortcut
property string currentShortcut
property string tool
property variant buttonsGroup: []
property string buttonGroup
property bool togglable: true
property int _buttonGroupIndex: -1
property var _buttonGroupArray: []
id: root
width: img.width + 5
height: img.height + 5
color: root.selected ? "#aa000000" : (mouseArea.containsMouse ? "#44000000" : "#00000000")
radius: 3
Component.onCompleted: {
var group = parent.buttonGroups[buttonGroup];
if (group) {
_buttonGroupArray = group;
for (var i = 0; i < _buttonGroupArray.length; ++i) {
if (_buttonGroupArray[i] === this) {
_buttonGroupIndex = i;
break;
}
}
}
}
ToolTip {
text: root.tooltip + " (" + root.shortcut + ")"
visible: mouseArea.containsMouse
@@ -68,10 +84,12 @@ Rectangle {
onClicked: {
if (!root.selected) {
for (var i = 0; i < root.buttonsGroup.length; ++i)
root.buttonsGroup[i].selected = false;
for (var i = 0; i < root._buttonGroupArray.length; ++i)
root._buttonGroupArray[i].selected = false;
root.selected = true;
if (_buttonGroupIndex >= 0)
_generalHelper.storeToolState(root.buttonGroup, root._buttonGroupIndex)
if (!root.togglable) {
// Deselect button after a short while (selection acts as simple click indicator)

View File

@@ -53,6 +53,10 @@ GeneralHelper::GeneralHelper()
m_overlayUpdateTimer.setSingleShot(true);
QObject::connect(&m_overlayUpdateTimer, &QTimer::timeout,
this, &GeneralHelper::overlayUpdateNeeded);
m_toolStateUpdateTimer.setSingleShot(true);
QObject::connect(&m_toolStateUpdateTimer, &QTimer::timeout,
this, &GeneralHelper::handlePendingToolStateUpdate);
}
void GeneralHelper::requestOverlayUpdate()
@@ -222,6 +226,32 @@ QQuick3DNode *GeneralHelper::resolvePick(QQuick3DNode *pickNode)
return pickNode;
}
void GeneralHelper::storeToolState(const QString &tool, const QVariant &state, int delay)
{
if (delay > 0) {
m_toolStatesPending.insert(tool, state);
m_toolStateUpdateTimer.start(delay);
} else {
if (m_toolStateUpdateTimer.isActive())
handlePendingToolStateUpdate();
QVariant theState;
// Convert JS arrays to QVariantLists for easier handling down the line
if (state.canConvert(QMetaType::QVariantList))
theState = state.value<QVariantList>();
else
theState = state;
if (m_toolStates[tool] != theState) {
m_toolStates.insert(tool, theState);
emit toolStateChanged(tool, theState);
}
}
}
void GeneralHelper::initToolStates(const QVariantMap &toolStates)
{
m_toolStates = toolStates;
}
bool GeneralHelper::isMacOS() const
{
#ifdef Q_OS_MACOS
@@ -231,6 +261,17 @@ bool GeneralHelper::isMacOS() const
#endif
}
void GeneralHelper::handlePendingToolStateUpdate()
{
m_toolStateUpdateTimer.stop();
auto it = m_toolStatesPending.constBegin();
while (it != m_toolStatesPending.constEnd()) {
storeToolState(it.key(), it.value());
++it;
}
m_toolStatesPending.clear();
}
}
}

View File

@@ -69,15 +69,22 @@ public:
Q_INVOKABLE void delayedPropertySet(QObject *obj, int delay, const QString &property,
const QVariant& value);
Q_INVOKABLE QQuick3DNode *resolvePick(QQuick3DNode *pickNode);
Q_INVOKABLE void storeToolState(const QString &tool, const QVariant &state, int delayEmit = 0);
Q_INVOKABLE void initToolStates(const QVariantMap &toolStates);
bool isMacOS() const;
signals:
void overlayUpdateNeeded();
void toolStateChanged(const QString &tool, const QVariant &toolState);
private:
void handlePendingToolStateUpdate();
QTimer m_overlayUpdateTimer;
QTimer m_toolStateUpdateTimer;
QVariantMap m_toolStates;
QVariantMap m_toolStatesPending;
};
}

View File

@@ -113,7 +113,7 @@ bool Qt5InformationNodeInstanceServer::eventFilter(QObject *, QEvent *event)
case QEvent::KeyPress: {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
QPair<int, int> data = {keyEvent->key(), keyEvent->modifiers()};
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Key_Pressed,
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::KeyPressed,
QVariant::fromValue(data)});
} break;
@@ -159,6 +159,8 @@ QObject *Qt5InformationNodeInstanceServer::createEditView3D(QQmlEngine *engine)
{
#ifdef QUICK3D_MODULE
auto helper = new QmlDesigner::Internal::GeneralHelper();
QObject::connect(helper, &QmlDesigner::Internal::GeneralHelper::toolStateChanged,
this, &Qt5InformationNodeInstanceServer::handleToolStateChanged);
engine->rootContext()->setContextProperty("_generalHelper", helper);
qmlRegisterType<QmlDesigner::Internal::MouseArea3D>("MouseArea3D", 1, 0, "MouseArea3D");
qmlRegisterType<QmlDesigner::Internal::CameraGeometry>("CameraGeometry", 1, 0, "CameraGeometry");
@@ -308,6 +310,14 @@ void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant
m_changedProperty = propertyName;
}
void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &tool,
const QVariant &toolState)
{
QPair<QString, QVariant> data = {tool, toolState};
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Edit3DToolState,
QVariant::fromValue(data)});
}
void Qt5InformationNodeInstanceServer::updateViewPortRect()
{
QRectF viewPortrect(0, 0, m_viewPortInstance.internalObject()->property("width").toDouble(),
@@ -476,7 +486,8 @@ ServerNodeInstance Qt5InformationNodeInstanceServer::findViewPort(
return ServerNodeInstance();
}
void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeInstance> &instanceList)
void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeInstance> &instanceList,
const QVariantMap &toolStates)
{
ServerNodeInstance root = rootNodeInstance();
@@ -513,6 +524,8 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeIns
}
createCameraAndLightGizmos(instanceList);
QMetaObject::invokeMethod(m_editView3D, "updateToolStates", Q_ARG(QVariant, toolStates));
}
}
@@ -627,7 +640,7 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com
nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList));
if (qEnvironmentVariableIsSet("QMLDESIGNER_QUICK3D_MODE"))
setup3DEditView(instanceList);
setup3DEditView(instanceList, command.edit3dToolStates());
}
void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList<ServerNodeInstance> &childList)

View File

@@ -61,6 +61,7 @@ private slots:
void handleSelectionChanged(const QVariant &objs);
void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName);
void handleObjectPropertyChange(const QVariant &object, const QVariant &propName);
void handleToolStateChanged(const QString &tool, const QVariant &toolState);
void updateViewPortRect();
protected:
@@ -78,7 +79,8 @@ private:
void handleObjectPropertyChangeTimeout();
void handleSelectionChangeTimeout();
QObject *createEditView3D(QQmlEngine *engine);
void setup3DEditView(const QList<ServerNodeInstance> &instanceList);
void setup3DEditView(const QList<ServerNodeInstance> &instanceList,
const QVariantMap &toolStates);
QObject *findRootNodeOf3DViewport(const QList<ServerNodeInstance> &instanceList) const;
void createCameraAndLightGizmos(const QList<ServerNodeInstance> &instanceList) const;
ServerNodeInstance findViewPort(const QList<ServerNodeInstance> &instanceList);

View File

@@ -211,6 +211,7 @@ private: // functions
ProjectExplorer::Target *m_currentTarget = nullptr;
int m_restartProcessTimerId;
RewriterTransaction m_puppetTransaction;
QVariantMap m_edit3DToolStates;
};
} // namespace ProxyNodeInstanceView

View File

@@ -979,7 +979,8 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand()
auxiliaryContainerVector,
importVector,
mockupTypesVector,
model()->fileUrl());
model()->fileUrl(),
m_edit3DToolStates);
}
ClearSceneCommand NodeInstanceView::createClearSceneCommand() const
@@ -1451,12 +1452,17 @@ void NodeInstanceView::library3DItemDropped(const Drop3DLibraryItemCommand &comm
void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command)
{
if (command.type() == PuppetToCreatorCommand::Key_Pressed) {
if (command.type() == PuppetToCreatorCommand::KeyPressed) {
QPair<int, int> data = qvariant_cast<QPair<int, int>>(command.data());
int key = data.first;
Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(data.second);
handlePuppetKeyPress(key, modifiers);
} else if (command.type() == PuppetToCreatorCommand::Edit3DToolState) {
if (!m_nodeInstanceServer.isNull()) {
auto data = qvariant_cast<QPair<QString, QVariant>>(command.data());
m_edit3DToolStates[data.first] = data.second;
}
}
}