Skip to content

Commit

Permalink
Snap canvas rotation around zero degrees
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
askmeaboutlo0m committed Sep 27, 2024
1 parent a9a838f commit 6aec278
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 8 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions src/desktop/dialogs/canvasshortcutsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
33 changes: 29 additions & 4 deletions src/desktop/scene/canvasview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1462,16 +1472,19 @@ 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;
int steps = m_zoomWheelDelta / 120;
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)));
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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:
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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:
Expand All @@ -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();
Expand Down Expand Up @@ -1988,16 +2009,18 @@ 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());
qreal a2 = qAtan2(hw - point.x(), hh - point.y());
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) {
Expand All @@ -2009,6 +2032,8 @@ void CanvasView::moveDrag(const QPoint &point)
m_dragDiscreteRotation += steps * -ROTATION_STEP_SIZE;
rotateByDiscreteSteps(steps);
}
} else {
setRotation(rotation() + r);
}
break;
}
Expand Down
2 changes: 2 additions & 0 deletions src/desktop/scene/canvasview.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
33 changes: 29 additions & 4 deletions src/desktop/view/canvascontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -701,16 +701,19 @@ 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;
int steps = m_zoomWheelDelta / 120;
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));
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1289,6 +1301,7 @@ void CanvasController::penPressEvent(
m_dragButton = button;
m_dragModifiers = modifiers;
m_dragDiscreteRotation = 0.0;
m_dragSnapRotation = this->rotation();
resetCursor();

} else if(
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -1492,6 +1509,8 @@ void CanvasController::moveDrag(const QPoint &point)
m_dragDiscreteRotation += steps * -DISCRETE_ROTATION_STEP;
rotateByDiscreteSteps(steps);
}
} else {
setRotation(rotation() + r);
}
break;
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/desktop/view/canvascontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 6aec278

Please sign in to comment.