From 84e537f313de5ecf969be3f7e3045a0ef8162cd3 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 25 Aug 2023 14:23:28 +0300 Subject: [PATCH] QmlDesigner: Add scale and rotation snapping to 3D view Fixes: QDS-10464 Change-Id: I9b327b21a3e09313664b2b4b47772e3cb4244327 Reviewed-by: Qt CI Patch Build Bot Reviewed-by: Mahmoud Badri --- .../SnapConfigurationDialog.qml | 141 ++++++++++++++---- .../components/edit3d/edit3dview.cpp | 56 ++++++- .../components/edit3d/edit3dview.h | 2 + .../components/edit3d/snapconfiguration.cpp | 30 +++- .../components/edit3d/snapconfiguration.h | 20 ++- .../qmldesigner/qmldesignerconstants.h | 2 + .../utils/designersettings.cpp | 4 + .../qmldesignerbase/utils/designersettings.h | 4 + .../qml2puppet/mockfiles/qt6/RotateRing.qml | 1 + .../qml2puppet/mockfiles/qt6/ScaleGizmo.qml | 1 + .../qml2puppet/editor3d/generalhelper.cpp | 93 ++++++++++-- .../qml2puppet/editor3d/generalhelper.h | 14 +- .../qml2puppet/editor3d/mousearea3d.cpp | 19 ++- .../qml2puppet/editor3d/mousearea3d.h | 3 + .../qt5informationnodeinstanceserver.cpp | 13 +- 15 files changed, 350 insertions(+), 53 deletions(-) diff --git a/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml b/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml index a4903997e77..d34a210ae8c 100644 --- a/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml +++ b/share/qtcreator/qmldesigner/edit3dQmlSource/SnapConfigurationDialog.qml @@ -23,49 +23,132 @@ Rectangle { Rectangle { id: ctrlRect width: root.width - 16 - height: posIntValue.height + 16 + height: posIntValue.height + rotIntValue.height + scaleIntValue.height + 32 color: StudioTheme.Values.themePanelBackground border.color: StudioTheme.Values.themeControlOutline border.width: StudioTheme.Values.border - Row { - x: 8 - y: 8 - width: posIntLabel.width + posIntValue.width + StudioTheme.Values.sectionRowSpacing - spacing: StudioTheme.Values.sectionRowSpacing - - Text { - id: posIntLabel - text: qsTr("Position Snap Interval:") - color: enabled ? StudioTheme.Values.themeTextColor - : StudioTheme.Values.themeTextColorDisabled - verticalAlignment: Qt.AlignVCenter - horizontalAlignment: Qt.AlignRight + Column { + padding: 8 + spacing: 8 + Row { height: posIntValue.height + width: parent.width - 16 + spacing: StudioTheme.Values.sectionRowSpacing + + Text { + id: posIntLabel + text: qsTr("Position Snap Interval:") + color: enabled ? StudioTheme.Values.themeTextColor + : StudioTheme.Values.themeTextColorDisabled + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignRight + height: posIntValue.height + } + + Item { // Spacer + width: Math.max(ctrlRect.width - posIntLabel.width - posIntValue.width - 32, 1) + height: 1 + } + + StudioControls.RealSpinBox { + id: posIntValue + realFrom: 1 + realTo: 100000 + realValue: rootView.posInt + realStepSize: 1 + width: 80 + actionIndicatorVisible: false + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Snap interval for move gizmo.") + ToolTip.delay: root.toolTipDelay + + onRealValueChanged: rootView.posInt = realValue + } } - StudioControls.RealSpinBox { - id: posIntValue - realFrom: 1 - realTo: 100000 - realValue: rootView.posInt - realStepSize: 1 - width: ctrlRect.width - 24 - posIntLabel.width - actionIndicatorVisible: false + Row { + height: rotIntValue.height + width: parent.width - 16 + spacing: StudioTheme.Values.sectionRowSpacing - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Snap interval for move gizmo.") - ToolTip.delay: root.toolTipDelay + Text { + id: rotIntLabel + text: qsTr("Rotation Snap Interval:") + color: enabled ? StudioTheme.Values.themeTextColor + : StudioTheme.Values.themeTextColorDisabled + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignRight + height: rotIntValue.height + } - onRealValueChanged: rootView.posInt = realValue + Item { // Spacer + width: Math.max(ctrlRect.width - rotIntLabel.width - rotIntValue.width - 32, 1) + height: 1 + } + + StudioControls.RealSpinBox { + id: rotIntValue + realFrom: 1 + realTo: 360 + realValue: rootView.rotInt + realStepSize: 1 + width: 80 + actionIndicatorVisible: false + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Snap interval in degrees for rotation gizmo.") + ToolTip.delay: root.toolTipDelay + + onRealValueChanged: rootView.rotInt = realValue + } + } + + Row { + height: scaleIntValue.height + width: parent.width - 16 + spacing: StudioTheme.Values.sectionRowSpacing + + Text { + id: scaleIntLabel + text: qsTr("Scale Snap Interval (%):") + color: enabled ? StudioTheme.Values.themeTextColor + : StudioTheme.Values.themeTextColorDisabled + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignRight + height: scaleIntValue.height + } + + Item { // Spacer + width: Math.max(ctrlRect.width - scaleIntLabel.width - scaleIntValue.width - 32, 1) + height: 1 + } + + StudioControls.RealSpinBox { + id: scaleIntValue + realFrom: 1 + realTo: 100000 + realValue: rootView.scaleInt + realStepSize: 1 + width: 80 + actionIndicatorVisible: false + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Snap interval for scale gizmo in percentage of original scale.") + ToolTip.delay: root.toolTipDelay + + onRealValueChanged: rootView.scaleInt = realValue + } } } } - Item { - id: spacer + Item { // Spacer width: 1 height: Math.max(root.height - buttons.height - ctrlRect.height - 16, 2) } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index a3f33120f52..4890c031a25 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -201,9 +201,15 @@ void Edit3DView::modelAttached(Model *model) AbstractView::modelAttached(model); rootModelNode().setAuxiliaryData(edit3dSnapPosProperty, m_snapPositionAction->action()->isChecked()); + rootModelNode().setAuxiliaryData(edit3dSnapRotProperty, m_snapRotationAction->action()->isChecked()); + rootModelNode().setAuxiliaryData(edit3dSnapScaleProperty, m_snapScaleAction->action()->isChecked()); rootModelNode().setAuxiliaryData(edit3dSnapAbsProperty, m_snapAbsoluteAction->action()->isChecked()); rootModelNode().setAuxiliaryData(edit3dSnapPosIntProperty, - Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION_INTERVAL)); + Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION_INTERVAL)); + rootModelNode().setAuxiliaryData(edit3dSnapRotIntProperty, + Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION_INTERVAL)); + rootModelNode().setAuxiliaryData(edit3dSnapScaleIntProperty, + Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL)); checkImports(); auto cachedImage = m_canvasCache.take(model); @@ -938,7 +944,7 @@ void Edit3DView::createEdit3DActions() QmlDesigner::Constants::EDIT3D_SNAP_POSITION, View3DActionType::Empty, QCoreApplication::translate("SnapPositionAction", "Snap Position"), - QKeySequence(Qt::SHIFT | Qt::Key_Tab), + QKeySequence(Qt::SHIFT | Qt::Key_W), true, Edit3DViewConfig::load(settingKeyForAction(QmlDesigner::Constants::EDIT3D_SNAP_POSITION), false).toBool(), QIcon(), @@ -946,6 +952,40 @@ void Edit3DView::createEdit3DActions() snapPositionTrigger, QCoreApplication::translate("SnapPositionAction", "Toggle position snapping during node drag.")); + SelectionContextOperation snapRotationTrigger = [this](const SelectionContext &) { + if (model()) + rootModelNode().setAuxiliaryData(edit3dSnapRotProperty, m_snapRotationAction->action()->isChecked()); + }; + + m_snapRotationAction = std::make_unique( + QmlDesigner::Constants::EDIT3D_SNAP_ROTATION, + View3DActionType::Empty, + QCoreApplication::translate("SnapRotationAction", "Snap Rotation"), + QKeySequence(Qt::SHIFT | Qt::Key_E), + true, + Edit3DViewConfig::load(settingKeyForAction(QmlDesigner::Constants::EDIT3D_SNAP_ROTATION), false).toBool(), + QIcon(), + this, + snapRotationTrigger, + QCoreApplication::translate("SnapRotationAction", "Toggle rotation snapping during node drag.")); + + SelectionContextOperation snapScaleTrigger = [this](const SelectionContext &) { + if (model()) + rootModelNode().setAuxiliaryData(edit3dSnapScaleProperty, m_snapScaleAction->action()->isChecked()); + }; + + m_snapScaleAction = std::make_unique( + QmlDesigner::Constants::EDIT3D_SNAP_SCALE, + View3DActionType::Empty, + QCoreApplication::translate("SnapScaleAction", "Snap Scale"), + QKeySequence(Qt::SHIFT | Qt::Key_R), + true, + Edit3DViewConfig::load(settingKeyForAction(QmlDesigner::Constants::EDIT3D_SNAP_SCALE), false).toBool(), + QIcon(), + this, + snapScaleTrigger, + QCoreApplication::translate("SnapScaleAction", "Toggle scale snapping during node drag.")); + SelectionContextOperation snapAbsoluteTrigger = [this](const SelectionContext &) { if (model()) rootModelNode().setAuxiliaryData(edit3dSnapAbsProperty, m_snapAbsoluteAction->action()->isChecked()); @@ -954,14 +994,14 @@ void Edit3DView::createEdit3DActions() m_snapAbsoluteAction = std::make_unique( QmlDesigner::Constants::EDIT3D_SNAP_ABSOLUTE, View3DActionType::Empty, - QCoreApplication::translate("SnapAbsoluteAction", "Absolute Snap"), - QKeySequence(Qt::SHIFT | Qt::Key_W), + QCoreApplication::translate("SnapAbsoluteAction", "Absolute Position Snap"), + QKeySequence(Qt::SHIFT | Qt::Key_A), true, Edit3DViewConfig::load(settingKeyForAction(QmlDesigner::Constants::EDIT3D_SNAP_ABSOLUTE), true).toBool(), QIcon(), this, snapAbsoluteTrigger, - QCoreApplication::translate("SnapAbsoluteAction", "If enabled, snapping uses scene origin as origin point.\nOtherwise snapping uses drag start point as origin point.")); + QCoreApplication::translate("SnapAbsoluteAction", "If enabled, position snapping uses scene origin as origin point.\nOtherwise snapping uses drag start point as origin point.")); m_leftActions << m_selectionModeAction.get(); m_leftActions << nullptr; // Null indicates separator @@ -1010,6 +1050,8 @@ void Edit3DView::createEdit3DActions() m_snapActions << m_snapConfigAction.get(); m_snapActions << m_snapPositionAction.get(); + m_snapActions << m_snapRotationAction.get(); + m_snapActions << m_snapScaleAction.get(); m_snapActions << m_snapAbsoluteAction.get(); } @@ -1123,6 +1165,10 @@ const char *Edit3DView::settingKeyForAction(const QByteArray &actionId) { if (actionId == Constants::EDIT3D_SNAP_POSITION) return DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION; + if (actionId == Constants::EDIT3D_SNAP_ROTATION) + return DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION; + if (actionId == Constants::EDIT3D_SNAP_SCALE) + return DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE; if (actionId == Constants::EDIT3D_SNAP_ABSOLUTE) return DesignerSettingsKey::EDIT3DVIEW_SNAP_ABSOLUTE; return ""; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 20967af87d0..65fcdf4b0ce 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -147,6 +147,8 @@ private: std::unique_ptr m_snapMenuAction; std::unique_ptr m_snapConfigAction; std::unique_ptr m_snapPositionAction; + std::unique_ptr m_snapRotationAction; + std::unique_ptr m_snapScaleAction; std::unique_ptr m_snapAbsoluteAction; std::unique_ptr m_bakeLightsAction; diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp index 0e3d90d007d..0f524e88071 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp @@ -54,14 +54,26 @@ void SnapConfiguration::apply() { Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION_INTERVAL, m_positionInterval); + Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION_INTERVAL, + m_rotationInterval); + Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL, + m_scaleInterval); m_view->rootModelNode().setAuxiliaryData(edit3dSnapPosIntProperty, m_positionInterval); + m_view->rootModelNode().setAuxiliaryData(edit3dSnapRotIntProperty, m_rotationInterval); + m_view->rootModelNode().setAuxiliaryData(edit3dSnapScaleIntProperty, m_scaleInterval); } void SnapConfiguration::showConfigDialog(const QPoint &pos) { double posInt = Edit3DViewConfig::load( DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION_INTERVAL, 10.).toDouble(); + double rotInt = Edit3DViewConfig::load( + DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION_INTERVAL, 15.).toDouble(); + double scaleInt = Edit3DViewConfig::load( + DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL, 10.).toDouble(); setPosInt(posInt); + setRotInt(rotInt); + setScaleInt(scaleInt); if (!m_configDialog) { // Show non-modal progress dialog with cancel button @@ -73,7 +85,7 @@ void SnapConfiguration::showConfigDialog(const QPoint &pos) m_configDialog->setFlags(Qt::Dialog); m_configDialog->setModality(Qt::ApplicationModal); m_configDialog->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_configDialog->setMinimumSize({250, 100}); + m_configDialog->setMinimumSize({280, 170}); m_configDialog->rootContext()->setContextProperties({ {"rootView", QVariant::fromValue(this)} @@ -98,6 +110,22 @@ void SnapConfiguration::setPosInt(double value) } } +void SnapConfiguration::setRotInt(double value) +{ + if (value != m_rotationInterval) { + m_rotationInterval = value; + emit rotIntChanged(); + } +} + +void SnapConfiguration::setScaleInt(double value) +{ + if (value != m_scaleInterval) { + m_scaleInterval = value; + emit scaleIntChanged(); + } +} + void SnapConfiguration::cleanup() { delete m_configDialog; diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h index 792d38ecb6e..e41dc7e75af 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h @@ -22,11 +22,21 @@ inline constexpr AuxiliaryDataKeyView edit3dSnapPosIntProperty{AuxiliaryDataType "snapPosInt3d"}; inline constexpr AuxiliaryDataKeyView edit3dSnapAbsProperty{AuxiliaryDataType::NodeInstanceAuxiliary, "snapAbs3d"}; +inline constexpr AuxiliaryDataKeyView edit3dSnapRotProperty{AuxiliaryDataType::NodeInstanceAuxiliary, + "snapRot3d"}; +inline constexpr AuxiliaryDataKeyView edit3dSnapRotIntProperty{AuxiliaryDataType::NodeInstanceAuxiliary, + "snapRotInt3d"}; +inline constexpr AuxiliaryDataKeyView edit3dSnapScaleProperty{AuxiliaryDataType::NodeInstanceAuxiliary, + "snapScale3d"}; +inline constexpr AuxiliaryDataKeyView edit3dSnapScaleIntProperty{AuxiliaryDataType::NodeInstanceAuxiliary, + "snapScaleInt3d"}; class SnapConfiguration : public QObject { Q_OBJECT Q_PROPERTY(double posInt READ posInt WRITE setPosInt NOTIFY posIntChanged) + Q_PROPERTY(double rotInt READ rotInt WRITE setRotInt NOTIFY rotIntChanged) + Q_PROPERTY(double scaleInt READ scaleInt WRITE setScaleInt NOTIFY scaleIntChanged) public: SnapConfiguration(AbstractView *view); @@ -39,9 +49,15 @@ public: void setPosInt(double value); double posInt() const { return m_positionInterval; } + void setRotInt(double value); + double rotInt() const { return m_rotationInterval; } + void setScaleInt(double value); + double scaleInt() const { return m_scaleInterval; } signals: void posIntChanged(); + void rotIntChanged(); + void scaleIntChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -51,7 +67,9 @@ private: QPointer m_configDialog; QPointer m_view; - double m_positionInterval = 11.; + double m_positionInterval = 10.; + double m_rotationInterval = 15.; + double m_scaleInterval = 10.; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index bbc66b75fe8..75938f1aa7c 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -66,6 +66,8 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC const char EDIT3D_BAKE_LIGHTS[] = "QmlDesigner.Editor3D.BakeLights"; const char EDIT3D_SNAP_MENU[] = "QmlDesigner.Editor3D.SnapMenu"; const char EDIT3D_SNAP_POSITION[] = "QmlDesigner.Editor3D.SnapPosition"; +const char EDIT3D_SNAP_ROTATION[] = "QmlDesigner.Editor3D.SnapRotation"; +const char EDIT3D_SNAP_SCALE[] = "QmlDesigner.Editor3D.SnapScale"; const char EDIT3D_SNAP_CONFIG[] = "QmlDesigner.Editor3D.SnapConfig"; const char EDIT3D_SNAP_ABSOLUTE[] = "QmlDesigner.Editor3D.SnapToGrid"; diff --git a/src/plugins/qmldesignerbase/utils/designersettings.cpp b/src/plugins/qmldesignerbase/utils/designersettings.cpp index 32f1c0f87bb..a19bb310a6f 100644 --- a/src/plugins/qmldesignerbase/utils/designersettings.cpp +++ b/src/plugins/qmldesignerbase/utils/designersettings.cpp @@ -86,6 +86,10 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_ABSOLUTE, true); restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION, false); restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_POSITION_INTERVAL, 10.); + restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION, false); + restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_ROTATION_INTERVAL, 15.); + restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE, false); + restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL, 10.); restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false); restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0); diff --git a/src/plugins/qmldesignerbase/utils/designersettings.h b/src/plugins/qmldesignerbase/utils/designersettings.h index 758715c0fc6..aa0b8b4b138 100644 --- a/src/plugins/qmldesignerbase/utils/designersettings.h +++ b/src/plugins/qmldesignerbase/utils/designersettings.h @@ -35,6 +35,10 @@ inline constexpr char EDIT3DVIEW_GRID_COLOR[] = "Edit3DViewGridLineColor"; inline constexpr char EDIT3DVIEW_SNAP_ABSOLUTE[] = "Edit3DViewSnapAbsolute"; inline constexpr char EDIT3DVIEW_SNAP_POSITION[] = "Edit3DViewSnapPosition"; inline constexpr char EDIT3DVIEW_SNAP_POSITION_INTERVAL[] = "Edit3DViewSnapPositionInterval"; +inline constexpr char EDIT3DVIEW_SNAP_ROTATION[] = "Edit3DViewSnapRotation"; +inline constexpr char EDIT3DVIEW_SNAP_ROTATION_INTERVAL[] = "Edit3DViewSnapRotationInterval"; +inline constexpr char EDIT3DVIEW_SNAP_SCALE[] = "Edit3DViewSnapScale"; +inline constexpr char EDIT3DVIEW_SNAP_SCALE_INTERVAL[] = "Edit3DViewSnapScaleInterval"; inline constexpr char ALWAYS_SAVE_IN_CRUMBLEBAR[] = "AlwaysSaveInCrumbleBar"; inline constexpr char USE_DEFAULT_PUPPET[] = "UseDefaultQml2Puppet"; inline constexpr char PUPPET_TOPLEVEL_BUILD_DIRECTORY[] = "PuppetToplevelBuildDirectory"; diff --git a/src/tools/qml2puppet/mockfiles/qt6/RotateRing.qml b/src/tools/qml2puppet/mockfiles/qt6/RotateRing.qml index eac1b0b46cb..914512603af 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/RotateRing.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/RotateRing.qml @@ -49,6 +49,7 @@ Model { currentAngle = mouseAreaMain.dragHelper.getNewRotationAngle( targetNode, _pointerPosPressed, Qt.vector3d(screenPos.x, screenPos.y, 0), _targetPosOnScreen, currentAngle, _trackBall); + currentAngle = _generalHelper.adjustRotationForSnap(currentAngle); mouseAreaMain.dragHelper.applyRotationAngleToNode(targetNode, _startRotation, currentAngle); } diff --git a/src/tools/qml2puppet/mockfiles/qt6/ScaleGizmo.qml b/src/tools/qml2puppet/mockfiles/qt6/ScaleGizmo.qml index 225c4e66430..37bc3cc7963 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/ScaleGizmo.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/ScaleGizmo.qml @@ -180,6 +180,7 @@ Node { var scaler = 1.0 + (yDelta * 0.025); if (scaler === 0) scaler = 0.0001; + scaler = _generalHelper.adjustScalerForSnap(scaler); return Qt.vector3d(scaler * _startScale.x, scaler * _startScale.y, scaler * _startScale.z); diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp index dfccf96a724..57b02f4b168 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.cpp @@ -771,6 +771,27 @@ bool GeneralHelper::isRotationBlocked(QQuick3DNode *node) const return m_rotationBlockedNodes.contains(node); } +// false is returned when keyboard modifiers result in no snapping. +// increment is adjusted according to keyboard modifiers +static bool queryKeyboardForSnapping(bool enabled, double &increment) +{ + if (increment <= 0.) + return false; + + // Need to do a hard query for key mods as puppet is not handling real events + Qt::KeyboardModifiers mods = QGuiApplication::queryKeyboardModifiers(); + const bool shiftMod = mods & Qt::ShiftModifier; + const bool ctrlMod = mods & Qt::ControlModifier; + + if ((!ctrlMod && !enabled) || (ctrlMod && enabled)) + return false; + + if (shiftMod) + increment *= 0.1; + + return true; +} + QVector3D GeneralHelper::adjustTranslationForSnap(const QVector3D &newPos, const QVector3D &startPos, const QVector3D &snapAxes, @@ -781,19 +802,10 @@ QVector3D GeneralHelper::adjustTranslationForSnap(const QVector3D &newPos, bool snapAbs = m_snapAbsolute; double increment = m_snapPositionInterval; - if (!node || increment == 0. || snapAxes.isNull() || qFuzzyIsNull((newPos - startPos).length())) + if (!node || snapAxes.isNull() || qFuzzyIsNull((newPos - startPos).length()) + || !queryKeyboardForSnapping(snapPos, increment)) { return newPos; - - // Need to do a hard query for key mods as puppet is not handling real events - Qt::KeyboardModifiers mods = QGuiApplication::queryKeyboardModifiers(); - const bool shiftMod = mods & Qt::ShiftModifier; - const bool ctrlMod = mods & Qt::ControlModifier; - - if ((!ctrlMod && !snapPos) || (ctrlMod && snapPos)) - return newPos; - - if (shiftMod) - increment *= 0.1; + } // The node is aligned if there is only 0/90/180/270 degree sceneRotation on the node // on the drag axis, or the drag plane normal for plane drags @@ -884,6 +896,63 @@ QVector3D GeneralHelper::adjustTranslationForSnap(const QVector3D &newPos, return newPos; } +// newAngle and return are radians +double GeneralHelper::adjustRotationForSnap(double newAngle) +{ + bool snapRot = m_snapRotation; + double increment = m_snapRotationInterval; + + if (qFuzzyIsNull(newAngle) || !queryKeyboardForSnapping(snapRot, increment)) + return newAngle; + + double angleDeg = qRadiansToDegrees(newAngle); + + double comp1 = double(int(angleDeg / increment)) * increment; + double comp2 = angleDeg > 0 ? comp1 + increment : comp1 - increment; + + return qAbs(angleDeg - comp1) > qAbs(angleDeg - comp2) ? + qDegreesToRadians(comp2) : qDegreesToRadians(comp1); +} + +static double adjustScaler(double newScale, double increment) +{ + double absScale = qAbs(newScale); + double comp1 = 1. + double(int((absScale / increment) - (1. / increment))) * increment; + double comp2 = comp1 + increment; + double retVal = absScale - comp1 > comp2 - absScale ? comp2 : comp1; + if (newScale < 0) + retVal *= -1.; + return retVal; +} + +double GeneralHelper::adjustScalerForSnap(double newScale) +{ + bool snapScale = m_snapScale; + double increment = m_snapScaleInterval; + + if (qFuzzyIsNull(newScale) || !queryKeyboardForSnapping(snapScale, increment)) + return newScale; + + return adjustScaler(newScale, increment); +} + +QVector3D GeneralHelper::adjustScaleForSnap(const QVector3D &newScale) +{ + bool snapScale = m_snapScale; + double increment = m_snapScaleInterval; + + if (qFuzzyIsNull(newScale.length()) || !queryKeyboardForSnapping(snapScale, increment)) + return newScale; + + QVector3D adjScale = newScale; + for (int i = 0; i < 3; ++i) { + if (!qFuzzyCompare(newScale[i], 1.f)) + adjScale[i] = adjustScaler(newScale[i], increment); + } + + return adjScale; +} + void GeneralHelper::handlePendingToolStateUpdate() { m_toolStateUpdateTimer.stop(); diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h index 5313e730d25..c9c960950cf 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/generalhelper.h @@ -106,9 +106,17 @@ public: const QVector3D &snapAxes, bool globalOrientation, QQuick3DNode *node); - void setSnapPosition(bool enable) { m_snapPosition = enable; } + Q_INVOKABLE double adjustRotationForSnap(double newAngle); + Q_INVOKABLE double adjustScalerForSnap(double newScale); + QVector3D adjustScaleForSnap(const QVector3D &newScale); + void setSnapAbsolute(bool enable) { m_snapAbsolute = enable; } + void setSnapPosition(bool enable) { m_snapPosition = enable; } + void setSnapRotation(bool enable) { m_snapRotation = enable; } + void setSnapScale(bool enable) { m_snapScale = enable; } void setSnapPositionInterval(double interval) { m_snapPositionInterval = interval; } + void setSnapRotationInterval(double interval) { m_snapRotationInterval = interval; } + void setSnapScaleInterval(double interval) { m_snapScaleInterval = interval / 100.; } signals: void overlayUpdateNeeded(); @@ -146,7 +154,11 @@ private: bool m_snapAbsolute = true; bool m_snapPosition = false; + bool m_snapRotation = false; + bool m_snapScale = false; double m_snapPositionInterval = 10.; + double m_snapRotationInterval = 15.; + double m_snapScaleInterval = .1; }; } diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.cpp index 2e78d84ac82..f4f22996e1f 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.cpp @@ -5,6 +5,8 @@ #include "mousearea3d.h" +#include "generalhelper.h" + #include #include #include @@ -16,6 +18,8 @@ namespace QmlDesigner { namespace Internal { +static GeneralHelper *s_generalHelper = nullptr; + // Double precision vector for cases where float calculations can suffer from rounding errors class DoubleVec3D { public: @@ -632,6 +636,10 @@ QVector3D MouseArea3D::getNewScale(const QVector3D &startScale, const QVector2D yScaler += axisY * relativeDistance.y() * distanceFactor; scaleVec *= xScaler; scaleVec *= yScaler; + + if (s_generalHelper) + scaleVec = s_generalHelper->adjustScaleForSnap(scaleVec); + return startScale * scaleVec; } @@ -713,12 +721,14 @@ void MouseArea3D::applyFreeRotation(QQuick3DNode *node, const QVector3D &startRo QVector3D yAxis = QVector3D(dataPtr[4], dataPtr[5], dataPtr[6]).normalized(); QVector3D finalAxis = (dragVector.x() * yAxis + dragVector.y() * xAxis); - qreal degrees = qRadiansToDegrees(qreal(finalAxis.length()) * mouseDragMultiplier()); + qreal radians = qreal(finalAxis.length()) * mouseDragMultiplier(); + if (s_generalHelper) + radians = s_generalHelper->adjustRotationForSnap(radians); finalAxis.normalize(); node->setEulerRotation(startRotation); - node->rotate(degrees, finalAxis, QQuick3DNode::SceneSpace); + node->rotate(qRadiansToDegrees(radians), finalAxis, QQuick3DNode::SceneSpace); } // Calculate scene position of the node's pivot point, which in practice is just the position @@ -818,6 +828,11 @@ QVector3D MouseArea3D::getMousePosInPlane(const MouseArea3D *helper, return sceneTrans.inverted().transform(intersectGlobalPos).toVec3(); } +void QmlDesigner::Internal::MouseArea3D::setGeneralHelper(GeneralHelper *helper) +{ + s_generalHelper = helper; +} + static QPoint getPosFromMoveEvent(QEvent *event) { switch (event->type()) { diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.h b/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.h index c0a81a0ee37..8d705c6eb0b 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/mousearea3d.h @@ -19,6 +19,8 @@ namespace QmlDesigner { namespace Internal { +class GeneralHelper; + class MouseArea3D : public QQuick3DNode { Q_OBJECT @@ -62,6 +64,7 @@ public: QVector3D getMousePosInPlane(const MouseArea3D *helper, const QPointF &mousePosInView) const; static qreal mouseDragMultiplier() { return .02; } + static void setGeneralHelper(GeneralHelper *helper); Q_INVOKABLE QVector3D rayIntersectsPlane(const QVector3D &rayPos0, const QVector3D &rayPos1, diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 867e79ffe04..203a2971ea5 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -376,10 +376,18 @@ void Qt5InformationNodeInstanceServer::updateSnapSettings(const QVectorsetSnapPosition(container.value().toBool()); - else if (container.name() == "snapAbs3d") - helper->setSnapAbsolute(container.value().toBool()); else if (container.name() == "snapPosInt3d") helper->setSnapPositionInterval(container.value().toDouble()); + else if (container.name() == "snapRot3d") + helper->setSnapRotation(container.value().toBool()); + else if (container.name() == "snapRotInt3d") + helper->setSnapRotationInterval(container.value().toDouble()); + else if (container.name() == "snapScale3d") + helper->setSnapScale(container.value().toBool()); + else if (container.name() == "snapScaleInt3d") + helper->setSnapScaleInterval(container.value().toDouble()); + else if (container.name() == "snapAbs3d") + helper->setSnapAbsolute(container.value().toBool()); } } #endif @@ -482,6 +490,7 @@ void Qt5InformationNodeInstanceServer::createEditView3D() engine()->addImageProvider(QLatin1String("IconGizmoImageProvider"), new QmlDesigner::Internal::IconGizmoImageProvider); m_3dHelper = helper; + Internal::MouseArea3D::setGeneralHelper(helper); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/EditView3D.qml"), m_editView3DData);