From 6aec278117b5f5176bb4c9c4ef86f48cce6d0fe5 Mon Sep 17 00:00:00 2001 From: askmeaboutloom Date: Fri, 27 Sep 2024 07:01:28 +0200 Subject: [PATCH] Snap canvas rotation around zero degrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To make it easier to reset the rotation without having to faff around with the angle dial or use a separate shortcut. The snapping happens at +-5°, which should be a small enough value that it's not particularly useful to rotate the canvas by that amount, but large enough that it's reasonably easy to hit. --- ChangeLog | 1 + src/desktop/dialogs/canvasshortcutsdialog.cpp | 4 +++ src/desktop/scene/canvasview.cpp | 33 ++++++++++++++++--- src/desktop/scene/canvasview.h | 2 ++ src/desktop/view/canvascontroller.cpp | 33 ++++++++++++++++--- src/desktop/view/canvascontroller.h | 2 ++ src/libclient/canvas/canvasshortcuts.h | 1 + 7 files changed, 68 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index ad26b00cba..d4b579786f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,6 +26,7 @@ Unreleased Version 2.2.2-pre * Server Feature: Allow checking session, operator and server account passwords via the API. This can be used to password-protect recordings or similar. Thanks Meru for contributing. * Fix: Correct some UI scaling problems with brush outlines, canvas centering, dock toggling, pixel grid and transform handles. Thanks annoy for reporting. * Feature: Extended touch tap gestures, among them two-finger tap to undo and three-finger tap to redo. Can be configured in the preferences under the Touch tab. Thanks InconsolableCellist and many others for suggesting. + * Feature: Snap canvas rotation around 0° by default. If you don't want this, you can set the canvas shortcut or touch rotation to "free rotate canvas" instead. 2024-08-09 Version 2.2.2-beta.3 * Fix: Use more accurate timers for performance profiles if the platform supports it. diff --git a/src/desktop/dialogs/canvasshortcutsdialog.cpp b/src/desktop/dialogs/canvasshortcutsdialog.cpp index 600bdfe0e5..166c2edcc0 100644 --- a/src/desktop/dialogs/canvasshortcutsdialog.cpp +++ b/src/desktop/dialogs/canvasshortcutsdialog.cpp @@ -31,6 +31,9 @@ CanvasShortcutsDialog::CanvasShortcutsDialog( d->ui.actionCombo->addItem(tr("Pan Canvas"), CanvasShortcuts::CANVAS_PAN); d->ui.actionCombo->addItem( tr("Rotate Canvas"), CanvasShortcuts::CANVAS_ROTATE); + d->ui.actionCombo->addItem( + //: This refers to rotating the canvas without snapping around 0°. + tr("Free Rotate Canvas"), CanvasShortcuts::CANVAS_ROTATE_NO_SNAP); d->ui.actionCombo->addItem( //: This refers to rotating the canvas in 15° steps. tr("Ratchet Rotate Canvas"), CanvasShortcuts::CANVAS_ROTATE_DISCRETE); @@ -194,6 +197,7 @@ void CanvasShortcutsDialog::updateAction() case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: showModifiers = true; diff --git a/src/desktop/scene/canvasview.cpp b/src/desktop/scene/canvasview.cpp index f7c657d26f..c10dc0021f 100644 --- a/src/desktop/scene/canvasview.cpp +++ b/src/desktop/scene/canvasview.cpp @@ -586,6 +586,11 @@ void CanvasView::rotateStepCounterClockwise() setRotation(rotation() - 5.0); } +void CanvasView::setRotationSnap(qreal degrees) +{ + setRotation(qAbs(std::fmod(degrees, 360.0)) < 5.0 ? 0.0 : degrees); +} + void CanvasView::rotateByDiscreteSteps(int steps) { constexpr qreal EPS = 0.01; @@ -736,6 +741,7 @@ void CanvasView::resetCursor() : Qt::OpenHandCursor); break; case CanvasShortcuts::CANVAS_ROTATE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: setViewportCursor(m_rotatecursor); break; case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: @@ -1111,6 +1117,7 @@ void CanvasView::penPressEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: if(m_dragmode != ViewDragMode::Started) { m_dragmode = ViewDragMode::Prepared; @@ -1142,6 +1149,7 @@ void CanvasView::penPressEvent( m_dragButton = button; m_dragModifiers = modifiers; m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = this->rotation(); resetCursor(); } else if( (button == Qt::LeftButton || button == Qt::RightButton || @@ -1301,6 +1309,7 @@ void CanvasView::penReleaseEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragmode = ViewDragMode::Prepared; @@ -1341,6 +1350,7 @@ void CanvasView::penReleaseEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_penmode = PenMode::Normal; m_dragmode = ViewDragMode::Prepared; @@ -1462,6 +1472,7 @@ void CanvasView::wheelEvent(QWheelEvent *event) break; case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: { event->accept(); m_zoomWheelDelta += deltaY; @@ -1469,9 +1480,11 @@ void CanvasView::wheelEvent(QWheelEvent *event) m_zoomWheelDelta -= steps * 120; if(steps != 0) { if(action == CanvasShortcuts::CANVAS_ROTATE) { - setRotation(rotation() + steps * 10); + setRotationSnap(rotation() + steps * 10); } else if(action == CanvasShortcuts::CANVAS_ROTATE_DISCRETE) { rotateByDiscreteSteps(steps); + } else if(action == CanvasShortcuts::CANVAS_ROTATE_NO_SNAP) { + setRotation(rotation() + steps * 10); } else { zoomStepsAt(steps, mapToCanvas(compat::wheelPosition(*event))); } @@ -1528,6 +1541,7 @@ void CanvasView::keyPressEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragAction = mouseMatch.action(); @@ -1550,6 +1564,7 @@ void CanvasView::keyPressEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_penmode = PenMode::Normal; @@ -1562,6 +1577,7 @@ void CanvasView::keyPressEvent(QKeyEvent *event) m_dragLastPoint = mapFromGlobal(QCursor::pos()); m_dragCanvasPoint = mapToCanvas(m_dragLastPoint); m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = rotation(); resetCursor(); break; default: @@ -1584,6 +1600,7 @@ void CanvasView::keyPressEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_penmode = PenMode::Normal; m_dragmode = ViewDragMode::Prepared; @@ -1632,6 +1649,7 @@ void CanvasView::keyReleaseEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragmode = ViewDragMode::None; @@ -1651,6 +1669,7 @@ void CanvasView::keyReleaseEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragmode = ViewDragMode::Started; @@ -1660,6 +1679,7 @@ void CanvasView::keyReleaseEvent(QKeyEvent *event) m_dragInverted = mouseMatch.inverted(); m_dragSwapAxes = mouseMatch.swapAxes(); m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = rotation(); resetCursor(); break; default: @@ -1681,6 +1701,7 @@ void CanvasView::keyReleaseEvent(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_dragmode = ViewDragMode::Prepared; m_dragAction = mouseMatch.action(); @@ -1988,7 +2009,8 @@ void CanvasView::moveDrag(const QPoint &point) scrollBy(deltaX, deltaY); break; case CanvasShortcuts::CANVAS_ROTATE: - case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: { + case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: { qreal hw = width() / 2.0; qreal hh = height() / 2.0; qreal a1 = qAtan2(hw - m_dragLastPoint.x(), hh - m_dragLastPoint.y()); @@ -1996,8 +2018,9 @@ void CanvasView::moveDrag(const QPoint &point) qreal ad = qRadiansToDegrees(a1 - a2); qreal r = m_dragInverted ? -ad : ad; if(m_dragAction == CanvasShortcuts::CANVAS_ROTATE) { - setRotation(rotation() + r); - } else { + m_dragSnapRotation += r; + setRotationSnap(m_dragSnapRotation); + } else if(m_dragAction == CanvasShortcuts::CANVAS_ROTATE_DISCRETE) { m_dragDiscreteRotation += r; qreal discrete = m_dragDiscreteRotation / ROTATION_STEP_SIZE; if(discrete >= 1.0) { @@ -2009,6 +2032,8 @@ void CanvasView::moveDrag(const QPoint &point) m_dragDiscreteRotation += steps * -ROTATION_STEP_SIZE; rotateByDiscreteSteps(steps); } + } else { + setRotation(rotation() + r); } break; } diff --git a/src/desktop/scene/canvasview.h b/src/desktop/scene/canvasview.h index fb262c5ddd..899797d1e2 100644 --- a/src/desktop/scene/canvasview.h +++ b/src/desktop/scene/canvasview.h @@ -335,6 +335,7 @@ private slots: void setViewportCursor(const QCursor &cursor); void setZoomToFit(Qt::Orientations orientations); + void setRotationSnap(qreal degrees); void rotateByDiscreteSteps(int steps); void viewRectChanged(); @@ -386,6 +387,7 @@ private slots: QPoint m_dragLastPoint; QPointF m_dragCanvasPoint; qreal m_dragDiscreteRotation; + qreal m_dragSnapRotation = 0.0; //! Previous pointer location canvas::Point m_prevpoint; diff --git a/src/desktop/view/canvascontroller.cpp b/src/desktop/view/canvascontroller.cpp index 8277e13e28..89375f1c8b 100644 --- a/src/desktop/view/canvascontroller.cpp +++ b/src/desktop/view/canvascontroller.cpp @@ -701,6 +701,7 @@ void CanvasController::handleWheel(QWheelEvent *event) break; case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: { event->accept(); m_zoomWheelDelta += deltaY; @@ -708,9 +709,11 @@ void CanvasController::handleWheel(QWheelEvent *event) m_zoomWheelDelta -= steps * 120; if(steps != 0) { if(action == CanvasShortcuts::CANVAS_ROTATE) { - setRotation(rotation() + steps * 10); + setRotationSnap(rotation() + steps * 10); } else if(action == CanvasShortcuts::CANVAS_ROTATE_DISCRETE) { rotateByDiscreteSteps(steps); + } else if(action == CanvasShortcuts::CANVAS_ROTATE_NO_SNAP) { + setRotation(rotation() + steps * 10); } else { zoomStepsAt(steps, mapPointToCanvasF(posf)); } @@ -769,6 +772,7 @@ void CanvasController::handleKeyPress(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragAction = mouseMatch.action(); @@ -792,6 +796,7 @@ void CanvasController::handleKeyPress(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_penMode = PenMode::Normal; @@ -804,6 +809,7 @@ void CanvasController::handleKeyPress(QKeyEvent *event) m_dragLastPoint = mapPointFromGlobal(QCursor::pos()); m_dragCanvasPoint = mapPointToCanvasF(m_dragLastPoint); m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = rotation(); updateOutline(); resetCursor(); break; @@ -823,6 +829,7 @@ void CanvasController::handleKeyPress(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_penMode = PenMode::Normal; m_dragMode = ViewDragMode::Prepared; @@ -872,6 +879,7 @@ void CanvasController::handleKeyRelease(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragMode = ViewDragMode::None; @@ -893,6 +901,7 @@ void CanvasController::handleKeyRelease(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragMode = ViewDragMode::Started; @@ -902,6 +911,7 @@ void CanvasController::handleKeyRelease(QKeyEvent *event) m_dragInverted = mouseMatch.inverted(); m_dragSwapAxes = mouseMatch.swapAxes(); m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = rotation(); updateOutline(); resetCursor(); break; @@ -924,6 +934,7 @@ void CanvasController::handleKeyRelease(QKeyEvent *event) case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_dragMode = ViewDragMode::Prepared; m_dragAction = mouseMatch.action(); @@ -1257,6 +1268,7 @@ void CanvasController::penPressEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: if(m_dragMode != ViewDragMode::Started) { m_dragMode = ViewDragMode::Prepared; @@ -1289,6 +1301,7 @@ void CanvasController::penPressEvent( m_dragButton = button; m_dragModifiers = modifiers; m_dragDiscreteRotation = 0.0; + m_dragSnapRotation = this->rotation(); resetCursor(); } else if( @@ -1353,6 +1366,7 @@ void CanvasController::penReleaseEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: case CanvasShortcuts::TOOL_ADJUST: m_dragMode = ViewDragMode::Prepared; @@ -1394,6 +1408,7 @@ void CanvasController::penReleaseEvent( case CanvasShortcuts::CANVAS_PAN: case CanvasShortcuts::CANVAS_ROTATE: case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: case CanvasShortcuts::CANVAS_ZOOM: m_penMode = PenMode::Normal; m_dragMode = ViewDragMode::Prepared; @@ -1470,7 +1485,8 @@ void CanvasController::moveDrag(const QPoint &point) scrollBy(deltaX, deltaY); break; case CanvasShortcuts::CANVAS_ROTATE: - case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: { + case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: { QSizeF halfSize = viewSizeF() / 2.0; qreal hw = halfSize.width(); qreal hh = halfSize.height(); @@ -1479,8 +1495,9 @@ void CanvasController::moveDrag(const QPoint &point) qreal ad = qRadiansToDegrees(a1 - a2); qreal r = m_dragInverted ? -ad : ad; if(m_dragAction == CanvasShortcuts::CANVAS_ROTATE) { - setRotation(rotation() + r); - } else { + m_dragSnapRotation += r; + setRotationSnap(m_dragSnapRotation); + } else if(m_dragAction == CanvasShortcuts::CANVAS_ROTATE_DISCRETE) { m_dragDiscreteRotation += r; qreal discrete = m_dragDiscreteRotation / DISCRETE_ROTATION_STEP; if(discrete >= 1.0) { @@ -1492,6 +1509,8 @@ void CanvasController::moveDrag(const QPoint &point) m_dragDiscreteRotation += steps * -DISCRETE_ROTATION_STEP; rotateByDiscreteSteps(steps); } + } else { + setRotation(rotation() + r); } break; } @@ -1531,6 +1550,7 @@ void CanvasController::resetCursor() : Qt::OpenHandCursor); break; case CanvasShortcuts::CANVAS_ROTATE: + case CanvasShortcuts::CANVAS_ROTATE_NO_SNAP: setViewportCursor(m_rotateCursor); break; case CanvasShortcuts::CANVAS_ROTATE_DISCRETE: @@ -1973,6 +1993,11 @@ void CanvasController::setZoomToFit(Qt::Orientations orientations) } } +void CanvasController::setRotationSnap(qreal degrees) +{ + setRotation(qAbs(std::fmod(degrees, 360.0)) < 5.0 ? 0.0 : degrees); +} + void CanvasController::rotateByDiscreteSteps(int steps) { constexpr qreal EPS = 0.01; diff --git a/src/desktop/view/canvascontroller.h b/src/desktop/view/canvascontroller.h index 01e24d4227..c75099ee21 100644 --- a/src/desktop/view/canvascontroller.h +++ b/src/desktop/view/canvascontroller.h @@ -294,6 +294,7 @@ class CanvasController : public QObject { bool isRotationInverted() const { return m_mirror ^ m_flip; } void setZoomToFit(Qt::Orientations orientations); + void setRotationSnap(qreal degrees); void rotateByDiscreteSteps(int steps); void emitTransformChanged(); @@ -399,6 +400,7 @@ class CanvasController : public QObject { QPoint m_dragLastPoint; QPointF m_dragCanvasPoint; qreal m_dragDiscreteRotation = 0.0; + qreal m_dragSnapRotation = 0.0; int m_zoomWheelDelta = 0; bool m_canvasSizeChanging = false; diff --git a/src/libclient/canvas/canvasshortcuts.h b/src/libclient/canvas/canvasshortcuts.h index 463df28da3..c8f12fda8d 100644 --- a/src/libclient/canvas/canvasshortcuts.h +++ b/src/libclient/canvas/canvasshortcuts.h @@ -30,6 +30,7 @@ class CanvasShortcuts { TOOL_ADJUST, CONSTRAINT, CANVAS_ROTATE_DISCRETE, + CANVAS_ROTATE_NO_SNAP, ACTION_COUNT, };