From 7215bb6291f9c25e2a863130a012b8cfc2370062 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Sat, 15 Nov 2025 09:28:34 +0100 Subject: [PATCH 1/3] rotation snapping by 15 degrees --- src/core/canvasmouseinteractions.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/canvasmouseinteractions.cpp b/src/core/canvasmouseinteractions.cpp index 4f5d89e84..3d317fdb3 100644 --- a/src/core/canvasmouseinteractions.cpp +++ b/src/core/canvasmouseinteractions.cpp @@ -60,6 +60,7 @@ #include #include #include +#include using namespace Friction::Core; @@ -851,6 +852,11 @@ void Canvas::rotateSelected(const eMouseEvent& e) rot = d_rot + mRotHalfCycles*180; } + if (!mValueInput.inputEnabled() && e.shiftMod()) { + constexpr qreal snapStep = 15.0; + rot = std::round(rot / snapStep) * snapStep; + } + if (mCurrentMode == CanvasMode::boxTransform) { rotateSelectedBy(rot, absPos, mStartTransform); } else { From 5720605019b16b2d0f65c7cb9f4cb7b1a23eebb1 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Sat, 15 Nov 2025 09:42:30 +0100 Subject: [PATCH 2/3] rotation snapping by 1 degree with ctrl/cmd modifier --- src/core/canvasmouseinteractions.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/canvasmouseinteractions.cpp b/src/core/canvasmouseinteractions.cpp index 3d317fdb3..9655158b9 100644 --- a/src/core/canvasmouseinteractions.cpp +++ b/src/core/canvasmouseinteractions.cpp @@ -852,9 +852,14 @@ void Canvas::rotateSelected(const eMouseEvent& e) rot = d_rot + mRotHalfCycles*180; } - if (!mValueInput.inputEnabled() && e.shiftMod()) { - constexpr qreal snapStep = 15.0; - rot = std::round(rot / snapStep) * snapStep; + if (!mValueInput.inputEnabled()) { + if (e.ctrlMod()) { + constexpr qreal snapStep = 1.0; + rot = std::round(rot / snapStep) * snapStep; + } else if (e.shiftMod()) { + constexpr qreal snapStep = 15.0; + rot = std::round(rot / snapStep) * snapStep; + } } if (mCurrentMode == CanvasMode::boxTransform) { From 80d90fa063efaae2f2c54b98c21936f47903f053 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Sat, 15 Nov 2025 18:15:30 +0100 Subject: [PATCH 3/3] add node tangent snapping every 15 degrees, it works for nodes and for keyframe nodes (timeline Graph) --- src/app/GUI/keysview.cpp | 31 +++++++++- src/core/MovablePoints/smartctrlpoint.h | 1 + src/core/canvas.cpp | 2 + src/core/canvas.h | 1 + src/core/canvasmouseinteractions.cpp | 75 ++++++++++++++++++------- 5 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/app/GUI/keysview.cpp b/src/app/GUI/keysview.cpp index 81e61815e..94644e69e 100644 --- a/src/app/GUI/keysview.cpp +++ b/src/app/GUI/keysview.cpp @@ -42,6 +42,8 @@ #include "timelinehighlightwidget.h" #include "GUI/dialogsinterface.h" #include "themesupport.h" +#include +#include KeysView::KeysView(BoxScrollWidget *boxesListVisible, QWidget *parent) : @@ -792,7 +794,9 @@ void KeysView::handleMouseMove(const QPoint &pos, dFrame = dX/mPixelsPerFrame; mValueInput.setDisplayedValue(dFrame); } + const auto mods = QApplication::keyboardModifiers(); const bool ctrlPt = mGPressedPoint && mGPressedPoint->isCtrlPt(); + const bool shiftPressed = mods & Qt::ShiftModifier; if(!ctrlPt) dFrame = round(dFrame); const qreal dDFrame = dFrame - mMoveDFrame; const int iDDFrame = qRound(dDFrame); @@ -809,8 +813,31 @@ void KeysView::handleMouseMove(const QPoint &pos, const QPointF saved = mGPressedPoint->getSavedFrameAndValue(); const qreal rawFrame = saved.x() + dFrameV; const qreal rawValue = saved.y() + dValue; - const qreal newFrame = qBound(mMinMoveFrame, rawFrame, mMaxMoveFrame); - const qreal newValue = qBound(mMinMoveVal, rawValue, mMaxMoveVal); + qreal newFrame = qBound(mMinMoveFrame, rawFrame, mMaxMoveFrame); + qreal newValue = qBound(mMinMoveVal, rawValue, mMaxMoveVal); + if(shiftPressed) { + if(const auto parentKey = mGPressedPoint->getParentKey()) { + const qreal keyFrame = parentKey->getRelFrame(); + const qreal keyValue = parentKey->getValueForGraph(); + const qreal dx = newFrame - keyFrame; + const qreal dy = newValue - keyValue; + const qreal dxPx = dx * mPixelsPerFrame; + const qreal dyPx = -dy * mPixelsPerValUnit; + const qreal lengthPx = qSqrt(dxPx*dxPx + dyPx*dyPx); + if(lengthPx > 0.0) { + constexpr qreal snapStep = 15.0; + const qreal angleDeg = qRadiansToDegrees(qAtan2(dyPx, dxPx)); + const qreal snappedDeg = std::round(angleDeg / snapStep) * snapStep; + const qreal snappedRad = qDegreesToRadians(snappedDeg); + const qreal snappedDxPx = lengthPx * qCos(snappedRad); + const qreal snappedDyPx = lengthPx * qSin(snappedRad); + newFrame = keyFrame + snappedDxPx / mPixelsPerFrame; + newValue = keyValue - snappedDyPx / mPixelsPerValUnit; + } + } + } + newFrame = qBound(mMinMoveFrame, newFrame, mMaxMoveFrame); + newValue = qBound(mMinMoveVal, newValue, mMaxMoveVal); mGPressedPoint->setFrameAndValue(newFrame, newValue, mPixelsPerFrame, mPixelsPerValUnit); diff --git a/src/core/MovablePoints/smartctrlpoint.h b/src/core/MovablePoints/smartctrlpoint.h index 1cfae5dd8..c7e0c0a7d 100644 --- a/src/core/MovablePoints/smartctrlpoint.h +++ b/src/core/MovablePoints/smartctrlpoint.h @@ -35,6 +35,7 @@ class CORE_EXPORT SmartCtrlPoint : public NonAnimatedMovablePoint { SmartCtrlPoint(SmartNodePoint * const parentPoint, const Type &type); public: + SmartNodePoint* getParentPoint() const { return mParentPoint_k; } void drawSk(SkCanvas* const canvas, const CanvasMode mode, const float invScale, diff --git a/src/core/canvas.cpp b/src/core/canvas.cpp index f6e587c51..6b3ffe234 100644 --- a/src/core/canvas.cpp +++ b/src/core/canvas.cpp @@ -1286,6 +1286,7 @@ bool Canvas::startScalingAction(const eKeyEvent &e) } mValueInput.clearAndDisableInput(); mValueInput.setupScale(); + mLastPointMoveBy = QPointF(); mRotPivot->setMousePos(e.fPos); mTransMode = TransformMode::scale; @@ -1301,6 +1302,7 @@ bool Canvas::startMovingAction(const eKeyEvent &e) mCurrentMode != CanvasMode::pointTransform) { return false; } mValueInput.clearAndDisableInput(); mValueInput.setupMove(); + mLastPointMoveBy = QPointF(); mTransMode = TransformMode::move; mDoubleClick = false; diff --git a/src/core/canvas.h b/src/core/canvas.h index 6bdeb5c6c..ac46340e2 100644 --- a/src/core/canvas.h +++ b/src/core/canvas.h @@ -935,6 +935,7 @@ class CORE_EXPORT Canvas : public CanvasBase bool mStartTransform = false; bool mSelecting = false; // bool mMoving = false; + QPointF mLastPointMoveBy; QRectF mSelectionRect; CanvasMode mCurrentMode = CanvasMode::boxTransform; diff --git a/src/core/canvasmouseinteractions.cpp b/src/core/canvasmouseinteractions.cpp index 9655158b9..b830f90c8 100644 --- a/src/core/canvasmouseinteractions.cpp +++ b/src/core/canvasmouseinteractions.cpp @@ -64,6 +64,11 @@ using namespace Friction::Core; +namespace { +constexpr qreal kDegToRad = static_cast(0.017453292519943295769); +constexpr qreal kRadToDeg = static_cast(57.2957795130823208768); +} + void Canvas::handleMovePathMousePressEvent(const eMouseEvent& e) { mPressedBox = mCurrentContainer->getBoxAt(e.fPos); @@ -212,6 +217,7 @@ void Canvas::handleLeftButtonMousePress(const eMouseEvent& e) //mMovesToSkip = 2; mStartTransform = true; mHasCreationPressPos = false; + mLastPointMoveBy = QPointF(); const qreal invScale = 1/e.fScale; const qreal invScaleUi = (qApp ? qApp->devicePixelRatio() : 1.0) * invScale; @@ -710,44 +716,72 @@ void Canvas::handleMovePointMouseMove(const eMouseEvent &e) } } + QPointF moveBy = getMoveByValueForEvent(e); + QPointF finalMoveBy = moveBy; + if ((mods & Qt::ShiftModifier) && mPressedPoint->isCtrlPoint()) { + const auto ctrlPoint = enve_cast(mPressedPoint.data()); + if (ctrlPoint) { + const auto parentPoint = ctrlPoint->getParentPoint(); + if (parentPoint) { + const QPointF parentAbs = parentPoint->getAbsolutePos(); + const QPointF startAbs = ctrlPoint->getAbsolutePos() - mLastPointMoveBy; + QPointF targetAbs = startAbs + moveBy; + const QPointF dir = targetAbs - parentAbs; + const qreal len = pointToLen(dir); + if (len > 0.0) { + constexpr qreal snapStep = 15.0; + const qreal angleDeg = std::atan2(dir.y(), dir.x()) * kRadToDeg; + const qreal snappedDeg = std::round(angleDeg / snapStep) * snapStep; + const qreal snappedRad = snappedDeg * kDegToRad; + const QPointF snappedVec(len * std::cos(snappedRad), + len * std::sin(snappedRad)); + targetAbs = parentAbs + snappedVec; + finalMoveBy = targetAbs - startAbs; + } + } + } + } + if (!mPressedPoint->selectionEnabled()) { if (mStartTransform) { mPressedPoint->startTransform(); mGridMoveStartPivot = mPressedPoint->getAbsolutePos(); } - - auto moveBy = getMoveByValueForEvent(e); if (snappingActive) { const auto snapped = moveBySnapTargets(e.fModifiers, - moveBy, + finalMoveBy, gridSettings, includeSelectedBounds, false, false); - if (snapped.first) { moveBy = snapped.second; } + if (snapped.first) { finalMoveBy = snapped.second; } } - mPressedPoint->moveByAbs(moveBy); + mPressedPoint->moveByAbs(finalMoveBy); + mLastPointMoveBy = finalMoveBy; return; } - } - if (mStartTransform && !mSelectedPoints_d.isEmpty()) { - mGridMoveStartPivot = getSelectedPointsAbsPivotPos(); - } + if (mStartTransform && !mSelectedPoints_d.isEmpty()) { + mGridMoveStartPivot = getSelectedPointsAbsPivotPos(); + } + if (snappingActive && !mSelectedPoints_d.isEmpty()) { + const auto snapped = moveBySnapTargets(e.fModifiers, + finalMoveBy, + gridSettings, + includeSelectedBounds, + false, + false); + if (snapped.first) { finalMoveBy = snapped.second; } + } - auto moveBy = getMoveByValueForEvent(e); - if (snappingActive && !mSelectedPoints_d.isEmpty()) { - const auto snapped = moveBySnapTargets(e.fModifiers, - moveBy, - gridSettings, - includeSelectedBounds, - false, - false); - if (snapped.first) { moveBy = snapped.second; } + moveSelectedPointsByAbs(finalMoveBy, mStartTransform); + mLastPointMoveBy = finalMoveBy; + } else { + const QPointF moveBy = getMoveByValueForEvent(e); + moveSelectedPointsByAbs(moveBy, mStartTransform); + mLastPointMoveBy = moveBy; } - - moveSelectedPointsByAbs(moveBy, mStartTransform); } } @@ -894,6 +928,7 @@ bool Canvas::prepareRotation(const QPointF &startPos, mTransMode = TransformMode::rotate; mRotHalfCycles = 0; mLastDRot = 0; + mLastPointMoveBy = QPointF(); mDoubleClick = false; mStartTransform = true;