Skip to content

Commit

Permalink
Make scrolling a bit less jumpy on touchpads
Browse files Browse the repository at this point in the history
Fixes #9
  • Loading branch information
mitchcurtis committed Sep 20, 2017
1 parent 6001968 commit 052a7ce
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 59 deletions.
26 changes: 17 additions & 9 deletions app/canvaspane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
#include "canvaspane.h"

#include <QJsonObject>
#include <QtMath>

CanvasPane::CanvasPane(QObject *parent) :
QObject(parent),
mSize(0.5),
mZoomLevel(1),
mZoomLevel(1.0),
mMaxZoomLevel(30),
mSceneCentered(true)
{
Expand All @@ -45,15 +46,20 @@ void CanvasPane::setSize(const qreal &size)
emit sizeChanged();
}

int CanvasPane::zoomLevel() const
qreal CanvasPane::zoomLevel() const
{
return mZoomLevel;
}

void CanvasPane::setZoomLevel(int zoomLevel)
int CanvasPane::integerZoomLevel() const
{
const int adjustedLevel = qBound(1, zoomLevel, mMaxZoomLevel);
if (adjustedLevel == mZoomLevel)
return qFloor(mZoomLevel);
}

void CanvasPane::setZoomLevel(qreal zoomLevel)
{
const qreal adjustedLevel = qBound(1.0, zoomLevel, qreal(mMaxZoomLevel));
if (qFuzzyCompare(adjustedLevel, mZoomLevel))
return;

mZoomLevel = adjustedLevel;
Expand All @@ -67,7 +73,7 @@ int CanvasPane::maxZoomLevel() const

QSize CanvasPane::zoomedSize(const QSize &size) const
{
return size * mZoomLevel;
return size * integerZoomLevel();
}

QPoint CanvasPane::offset() const
Expand All @@ -86,7 +92,7 @@ void CanvasPane::setOffset(const QPoint &offset)

QPoint CanvasPane::zoomedOffset() const
{
return mOffset * zoomLevel();
return mOffset * integerZoomLevel();
}

bool CanvasPane::isSceneCentered() const
Expand All @@ -110,7 +116,9 @@ void CanvasPane::read(const QJsonObject &json)
void CanvasPane::write(QJsonObject &json) const
{
json[QLatin1String("size")] = mSize;
json[QLatin1String("zoomLevel")] = mZoomLevel;
// It's only important that the zoom level is a real while zooming
// to ensure that zooming is not too quick.
json[QLatin1String("zoomLevel")] = integerZoomLevel();
json[QLatin1String("offsetX")] = mOffset.x();
json[QLatin1String("offsetY")] = mOffset.y();
json[QLatin1String("sceneCentered")] = mSceneCentered;
Expand All @@ -119,7 +127,7 @@ void CanvasPane::write(QJsonObject &json) const
void CanvasPane::reset()
{
setSize(0.5);
setZoomLevel(1);
setZoomLevel(1.0);
setOffset(QPoint(0, 0));
setSceneCentered(true);
}
10 changes: 6 additions & 4 deletions app/canvaspane.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class CanvasPane : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(int zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged)
// Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged)
Q_PROPERTY(int integerZoomLevel READ integerZoomLevel NOTIFY zoomLevelChanged)
Q_PROPERTY(int maxZoomLevel READ maxZoomLevel CONSTANT)
Q_PROPERTY(QPoint offset READ offset WRITE setOffset NOTIFY offsetChanged)

Expand All @@ -40,8 +41,9 @@ class CanvasPane : public QObject
qreal size() const;
void setSize(const qreal &size);

int zoomLevel() const;
void setZoomLevel(int zoomLevel);
qreal zoomLevel() const;
int integerZoomLevel() const;
void setZoomLevel(qreal zoomLevel);
int maxZoomLevel() const;

QSize zoomedSize(const QSize &size) const;
Expand All @@ -66,7 +68,7 @@ class CanvasPane : public QObject

private:
qreal mSize;
int mZoomLevel;
qreal mZoomLevel;
int mMaxZoomLevel;
// From the top left of the canvas.
QPoint mOffset;
Expand Down
38 changes: 21 additions & 17 deletions app/imagecanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ void ImageCanvas::drawPane(QPainter *painter, const CanvasPane &pane, int paneIn
QVector<qreal> dashes;
dashes << 4 << 4;
pen.setDashPattern(dashes);
const QRect zoomedSelectionArea(mSelectionArea.topLeft() * pane.zoomLevel(), pane.zoomedSize(mSelectionArea.size()));
const QRect zoomedSelectionArea(mSelectionArea.topLeft() * pane.integerZoomLevel(), pane.zoomedSize(mSelectionArea.size()));
painter->setPen(pen);
painter->drawRect(zoomedSelectionArea);

Expand Down Expand Up @@ -829,7 +829,7 @@ void ImageCanvas::drawGuide(QPainter *painter, const CanvasPane &pane, int paneI
const bool draggingExistingGuide = mPressedGuideIndex != -1 && mPressedGuideIndex == guideIndex;
const bool vertical = guide.orientation() == Qt::Vertical;
const int guidePosition = draggingExistingGuide ? (vertical ? mCursorSceneX : mCursorSceneY) : guide.position();
const qreal zoomedGuidePosition = guidePosition * pane.zoomLevel() + painter->pen().widthF() / 2.0;
const qreal zoomedGuidePosition = guidePosition * pane.integerZoomLevel() + painter->pen().widthF() / 2.0;

if (vertical) {
// Don't need to account for the vertical offset anymore, as vertical guides go across the whole height of the pane.
Expand All @@ -854,14 +854,14 @@ void ImageCanvas::centrePanes(bool respectSceneCentred)
return;

if (!respectSceneCentred || (respectSceneCentred && mFirstPane.isSceneCentered())) {
const QPoint newOffset(paneWidth(0) / 2 - (mProject->widthInPixels() * mFirstPane.zoomLevel()) / 2,
height() / 2 - (mProject->heightInPixels() * mFirstPane.zoomLevel()) / 2);
const QPoint newOffset(paneWidth(0) / 2 - (mProject->widthInPixels() * mFirstPane.integerZoomLevel()) / 2,
height() / 2 - (mProject->heightInPixels() * mFirstPane.integerZoomLevel()) / 2);
mFirstPane.setOffset(newOffset);
}

if (!respectSceneCentred || (respectSceneCentred && mSecondPane.isSceneCentered())) {
const QPoint newOffset(paneWidth(1) / 2 - (mProject->widthInPixels() * mFirstPane.zoomLevel()) / 2,
height() / 2 - (mProject->heightInPixels() * mFirstPane.zoomLevel()) / 2);
const QPoint newOffset(paneWidth(1) / 2 - (mProject->widthInPixels() * mFirstPane.integerZoomLevel()) / 2,
height() / 2 - (mProject->heightInPixels() * mFirstPane.integerZoomLevel()) / 2);
mSecondPane.setOffset(newOffset);
}

Expand Down Expand Up @@ -1280,7 +1280,7 @@ void ImageCanvas::zoomIn()
if (!pane)
return;

pane->setZoomLevel(pane->zoomLevel() + 1);
pane->setZoomLevel(pane->integerZoomLevel() + 1);
}

void ImageCanvas::zoomOut()
Expand All @@ -1289,7 +1289,7 @@ void ImageCanvas::zoomOut()
if (!pane)
return;

pane->setZoomLevel(pane->zoomLevel() - 1);
pane->setZoomLevel(pane->integerZoomLevel() - 1);
}

void ImageCanvas::flipSelection(Qt::Orientation orientation)
Expand Down Expand Up @@ -1588,8 +1588,8 @@ void ImageCanvas::updateCursorPos(const QPoint &eventPos)
}

// We need the position as floating point numbers so that pen sizes > 1 work properly.
mCursorSceneFX = qreal(mCursorPaneX - mCurrentPane->offset().x()) / mCurrentPane->zoomLevel();
mCursorSceneFY = qreal(mCursorPaneY - mCurrentPane->offset().y()) / mCurrentPane->zoomLevel();
mCursorSceneFX = qreal(mCursorPaneX - mCurrentPane->offset().x()) / mCurrentPane->integerZoomLevel();
mCursorSceneFY = qreal(mCursorPaneY - mCurrentPane->offset().y()) / mCurrentPane->integerZoomLevel();
setCursorSceneX(mCursorSceneFX);
setCursorSceneY(mCursorSceneFY);

Expand Down Expand Up @@ -1703,10 +1703,10 @@ void ImageCanvas::updateWindowCursorShape()

void ImageCanvas::onZoomLevelChanged()
{
mFirstHorizontalRuler->setZoomLevel(mFirstPane.zoomLevel());
mFirstVerticalRuler->setZoomLevel(mFirstPane.zoomLevel());
mSecondHorizontalRuler->setZoomLevel(mSecondPane.zoomLevel());
mSecondVerticalRuler->setZoomLevel(mSecondPane.zoomLevel());
mFirstHorizontalRuler->setZoomLevel(mFirstPane.integerZoomLevel());
mFirstVerticalRuler->setZoomLevel(mFirstPane.integerZoomLevel());
mSecondHorizontalRuler->setZoomLevel(mSecondPane.integerZoomLevel());
mSecondVerticalRuler->setZoomLevel(mSecondPane.integerZoomLevel());

update();
}
Expand Down Expand Up @@ -1789,14 +1789,18 @@ void ImageCanvas::wheelEvent(QWheelEvent *event)

mCurrentPane->setSceneCentered(false);

const int oldZoomLevel = mCurrentPane->zoomLevel();
const int newZoomLevel = oldZoomLevel + (event->angleDelta().y() > 0 ? 1 : -1);
const int oldZoomLevel = mCurrentPane->integerZoomLevel();
const qreal zoomAmount = 0.15;
const qreal newZoomLevel = mCurrentPane->zoomLevel() + (event->angleDelta().y() > 0 ? zoomAmount : -zoomAmount);
mCurrentPane->setZoomLevel(newZoomLevel);

// From: http://stackoverflow.com/a/38302057/904422
QPoint relativeEventPos = eventPosRelativeToCurrentPane(event->pos());
// We still want to use integer zoom levels here; the real-based zoom level just allows
// smaller changes in zoom level, rather than incrementing/decrementing by one every time
// we get a wheel event.
mCurrentPane->setOffset(relativeEventPos -
float(mCurrentPane->zoomLevel()) / float(oldZoomLevel) * (relativeEventPos - mCurrentPane->offset()));
float(mCurrentPane->integerZoomLevel()) / float(oldZoomLevel) * (relativeEventPos - mCurrentPane->offset()));
}

void ImageCanvas::mousePressEvent(QMouseEvent *event)
Expand Down
2 changes: 1 addition & 1 deletion app/qml/ui/ImageTypeCanvas.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ImageCanvas {
scrollZoom: settings.scrollZoom
anchors.fill: parent

readonly property int currentPaneZoomLevel: imageCanvas.currentPane ? imageCanvas.currentPane.zoomLevel : 1
readonly property int currentPaneZoomLevel: imageCanvas.currentPane ? imageCanvas.currentPane.integerZoomLevel : 1
readonly property point currentPaneOffset: imageCanvas.currentPane ? imageCanvas.currentPane.offset : Qt.point(0, 0)
readonly property bool useCrosshairCursor: imageCanvas.tool === TileCanvas.SelectionTool
|| (imageCanvas.toolSize < 4 && imageCanvas.currentPaneZoomLevel <= 3)
Expand Down
2 changes: 1 addition & 1 deletion app/qml/ui/LayeredImageTypeCanvas.qml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ LayeredImageCanvas {
scrollZoom: settings.scrollZoom
anchors.fill: parent

readonly property int currentPaneZoomLevel: layeredCanvas.currentPane ? layeredCanvas.currentPane.zoomLevel : 1
readonly property int currentPaneZoomLevel: layeredCanvas.currentPane ? layeredCanvas.currentPane.integerZoomLevel : 1
readonly property point currentPaneOffset: layeredCanvas.currentPane ? layeredCanvas.currentPane.offset : Qt.point(0, 0)
readonly property bool useCrosshairCursor: layeredCanvas.tool === ImageCanvas.SelectionTool
|| (layeredCanvas.toolSize < 4 && layeredCanvas.currentPaneZoomLevel <= 3)
Expand Down
2 changes: 1 addition & 1 deletion app/qml/ui/TilesetTypeCanvas.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TileCanvas {
scrollZoom: settings.scrollZoom
anchors.fill: parent

readonly property int currentPaneZoomLevel: tileCanvas.currentPane ? tileCanvas.currentPane.zoomLevel : 1
readonly property int currentPaneZoomLevel: tileCanvas.currentPane ? tileCanvas.currentPane.integerZoomLevel : 1
readonly property point currentPaneOffset: tileCanvas.currentPane ? tileCanvas.currentPane.offset : Qt.point(0, 0)
readonly property bool useCrosshairCursor: tileCanvas.mode === TileCanvas.TileMode
|| tileCanvas.tool === TileCanvas.SelectionTool || (tileCanvas.toolSize < 4 && tileCanvas.currentPaneZoomLevel <= 3)
Expand Down
2 changes: 1 addition & 1 deletion app/qml/ui/ZoomIndicator.qml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ RowLayout {

Label {
id: zoomLevelText
text: pane ? pane.zoomLevel : ""
text: pane ? pane.integerZoomLevel : ""
color: "#ffffff"

Layout.minimumWidth: maxZoomTextMetrics.width
Expand Down
5 changes: 3 additions & 2 deletions app/tilecanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,9 @@ void TileCanvas::updateCursorPos(const QPoint &eventPos)
}

// We need the position as floating point numbers so that pen sizes > 1 work properly.
mCursorSceneFX = qreal(mCursorPaneX - mCurrentPane->offset().x()) / mTilesetProject->tileWidth() / mCurrentPane->zoomLevel() * mTilesetProject->tileWidth();
mCursorSceneFY = qreal(mCursorPaneY - mCurrentPane->offset().y()) / mTilesetProject->tileHeight() / mCurrentPane->zoomLevel() * mTilesetProject->tileHeight();
const int zoomLevel = mCurrentPane->integerZoomLevel();
mCursorSceneFX = qreal(mCursorPaneX - mCurrentPane->offset().x()) / mTilesetProject->tileWidth() / zoomLevel * mTilesetProject->tileWidth();
mCursorSceneFY = qreal(mCursorPaneY - mCurrentPane->offset().y()) / mTilesetProject->tileHeight() / zoomLevel * mTilesetProject->tileHeight();
mCursorSceneX = mCursorSceneFX;
mCursorSceneY = mCursorSceneFY;

Expand Down
13 changes: 13 additions & 0 deletions tests/testhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1396,3 +1396,16 @@ void TestHelper::panBy(int xDistance, int yDistance)
QCOMPARE(window->cursor().shape(), Qt::BlankCursor);
QCOMPARE(canvas->currentPane()->offset(), expectedOffset);
}

void TestHelper::zoomTo(int zoomLevel)
{
zoomTo(zoomLevel, cursorWindowPos);
}

void TestHelper::zoomTo(int zoomLevel, const QPoint &pos)
{
CanvasPane *currentPane = canvas->currentPane();
for (int i = 0; currentPane->zoomLevel() < zoomLevel; ++i)
wheelEvent(canvas, pos, 1);
QCOMPARE(currentPane->integerZoomLevel(), qreal(zoomLevel));
}
2 changes: 2 additions & 0 deletions tests/testhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ private Q_SLOTS:
void switchTool(ImageCanvas::Tool tool);
void panTopLeftTo(int x, int y);
void panBy(int xDistance, int yDistance);
void zoomTo(int zoomLevel);
void zoomTo(int zoomLevel, const QPoint &pos);
void changeCanvasSize(int width, int height);
void changeToolSize(int size);
int sliderValue(QQuickItem *slider) const;
Expand Down
36 changes: 13 additions & 23 deletions tests/tst_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,7 @@ void tst_App::saveAsAndLoad()

// Zoom in.
setCursorPosInPixels(QPoint(0, 0));
const int expectedZoomLevel = 5;
CanvasPane *currentPane = canvas->currentPane();
for (int i = 0; currentPane->zoomLevel() < expectedZoomLevel && i < expectedZoomLevel; ++i)
wheelEvent(canvas, cursorWindowPos, 1);
QCOMPARE(currentPane->zoomLevel(), expectedZoomLevel);
zoomTo(5);

QCOMPARE(canvas->isSplitScreen(), true);

Expand All @@ -406,10 +402,10 @@ void tst_App::saveAsAndLoad()

// Store the original offsets, etc.
const QPoint firstPaneOffset = canvas->firstPane()->offset();
const int firstPaneZoomLevel = canvas->firstPane()->zoomLevel();
const int firstPaneZoomLevel = canvas->firstPane()->integerZoomLevel();
const qreal firstPaneSize = canvas->firstPane()->size();
const QPoint secondPaneOffset = canvas->secondPane()->offset();
const int secondPaneZoomLevel = canvas->secondPane()->zoomLevel();
const int secondPaneZoomLevel = canvas->secondPane()->integerZoomLevel();
const qreal secondPaneSize = canvas->secondPane()->size();

// Save the project.
Expand All @@ -427,10 +423,10 @@ void tst_App::saveAsAndLoad()
QCOMPARE(project->guides().size(), 1);
QCOMPARE(project->guides().first().position(), 10);
QCOMPARE(canvas->firstPane()->offset(), firstPaneOffset);
QCOMPARE(canvas->firstPane()->zoomLevel(), firstPaneZoomLevel);
QCOMPARE(canvas->firstPane()->integerZoomLevel(), firstPaneZoomLevel);
QCOMPARE(canvas->firstPane()->size(), firstPaneSize);
QCOMPARE(canvas->secondPane()->offset(), secondPaneOffset);
QCOMPARE(canvas->secondPane()->zoomLevel(), secondPaneZoomLevel);
QCOMPARE(canvas->secondPane()->integerZoomLevel(), secondPaneZoomLevel);
QCOMPARE(canvas->secondPane()->size(), secondPaneSize);
}

Expand Down Expand Up @@ -1237,9 +1233,7 @@ void tst_App::zoomAndPan()
panBy(50, 0);

// Test zoom.
QPoint zoomPos = tileSceneCentre(5, 5);
wheelEvent(tileCanvas, zoomPos, 1);
QCOMPARE(tileCanvas->currentPane()->zoomLevel(), 2);
zoomTo(2, tileSceneCentre(5, 5));
}

void tst_App::zoomAndCentre()
Expand All @@ -1253,16 +1247,12 @@ void tst_App::zoomAndCentre()
QCOMPARE(currentPane, tileCanvas->firstPane());

// Zoom in.
QPoint zoomPos = tileSceneCentre(5, 5);
const int expectedZoomLevel = 5;
for (int i = 0; currentPane->zoomLevel() < expectedZoomLevel && i < expectedZoomLevel; ++i)
wheelEvent(tileCanvas, zoomPos, 1);
QCOMPARE(currentPane->zoomLevel(), expectedZoomLevel);
zoomTo(5, tileSceneCentre(5, 5));

triggerCentre();
const QPoint expectedOffset(
currentPane->size() * tileCanvas->width() / 2 - (tilesetProject->widthInPixels() * currentPane->zoomLevel()) / 2,
tileCanvas->height() / 2 - (tilesetProject->heightInPixels() * currentPane->zoomLevel()) / 2);
currentPane->size() * tileCanvas->width() / 2 - (tilesetProject->widthInPixels() * currentPane->integerZoomLevel()) / 2,
tileCanvas->height() / 2 - (tilesetProject->heightInPixels() * currentPane->integerZoomLevel()) / 2);
// A one pixel difference was introduced here at some point.. not sure why, but it's not important.
const int xDiff = qAbs(currentPane->offset().x() - expectedOffset.x());
const int yDiff = qAbs(currentPane->offset().y() - expectedOffset.y());
Expand Down Expand Up @@ -1302,15 +1292,15 @@ void tst_App::penWhilePannedAndZoomed()
panBy(xDistance, yDistance);

if (zoomLevel > 1) {
for (int i = 0; i < zoomLevel - canvas->currentPane()->zoomLevel(); ++i) {
for (int i = 0; i < zoomLevel - canvas->currentPane()->integerZoomLevel(); ++i) {
wheelEvent(canvas, tileSceneCentre(5, 5), 1);
}
QCOMPARE(canvas->currentPane()->zoomLevel(), zoomLevel);
QCOMPARE(canvas->currentPane()->integerZoomLevel(), zoomLevel);
} else if (zoomLevel < 1) {
for (int i = 0; i < qAbs(zoomLevel - canvas->currentPane()->zoomLevel()); ++i) {
for (int i = 0; i < qAbs(zoomLevel - canvas->currentPane()->integerZoomLevel()); ++i) {
wheelEvent(canvas, tileSceneCentre(5, 5), -1);
}
QCOMPARE(canvas->currentPane()->zoomLevel(), zoomLevel);
QCOMPARE(canvas->currentPane()->integerZoomLevel(), zoomLevel);
}

if (projectType == Project::TilesetType) {
Expand Down

0 comments on commit 052a7ce

Please sign in to comment.