diff --git a/libs/widgetutils/kis_zoom_scrollbar.cpp b/libs/widgetutils/kis_zoom_scrollbar.cpp index ecaa98c85d..db9baa18a3 100644 --- a/libs/widgetutils/kis_zoom_scrollbar.cpp +++ b/libs/widgetutils/kis_zoom_scrollbar.cpp @@ -1,189 +1,229 @@ /* * Copyright (c) 2020 Eoin O'Neill * Copyright (c) 2020 Emmet O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_scrollbar.h" #include "kis_global.h" #include "kis_debug.h" #include +#include KisZoomableScrollbar::KisZoomableScrollbar(QWidget *parent) : QScrollBar(parent) - , previousPosition(0,0) - , accelAccumulator(0,0) - , scrollAccumulator(0.0f) - , zoomDeadzone(0.90f) + , lastKnownPosition(0,0) + , accelerationAccumulator(0,0) + , scrollSubPixelAccumulator(0.0f) + , zoomPerpendicularityThreshold(0.75f) , catchTeleportCorrection(false) { } KisZoomableScrollbar::KisZoomableScrollbar(Qt::Orientation orientation, QWidget *parent) : KisZoomableScrollbar(parent) { setOrientation(orientation); } KisZoomableScrollbar::~KisZoomableScrollbar() { } -void KisZoomableScrollbar::mousePressEvent(QMouseEvent *event) -{ - const bool wasSliderDownBefore = isSliderDown(); - QScrollBar::mousePressEvent(event); - - if( isSliderDown() && !wasSliderDownBefore ){ - previousPosition = mapToGlobal(event->pos()); - accelAccumulator = QVector2D(0,0); - setCursor(Qt::BlankCursor); - } else { - setCursor(Qt::ArrowCursor); +bool KisZoomableScrollbar::catchTeleports(QMouseEvent *event) { + if (catchTeleportCorrection) { + catchTeleportCorrection = false; + event->accept(); + return true; } + return false; } -void KisZoomableScrollbar::mouseMoveEvent(QMouseEvent *event) +void KisZoomableScrollbar::handleWrap( const QPoint &accel, const QPoint &globalMouseCoord) { - if (isSliderDown()) { - //Catch for teleportation from one side of the screen to the other. - if (catchTeleportCorrection) { - catchTeleportCorrection = false; - event->accept(); - return; + QRect windowRect = window()->geometry(); + windowRect = kisGrowRect(windowRect, -2); + const bool xWrap = true; + const bool yWrap = true; + if (!windowRect.contains(globalMouseCoord)) { + int x = globalMouseCoord.x(); + int y = globalMouseCoord.y(); + + if (x < windowRect.x() ) { + if (xWrap) { + x += windowRect.width(); + } else { + x = (windowRect.x() + 2); + } + } else if (x > (windowRect.x() + windowRect.width()) ) { + if (xWrap) { + x -= windowRect.width(); + } else { + x = windowRect.x() + (windowRect.width() - 2); + } } - QPoint globalMouseCoord = mapToGlobal(event->pos()); - QPoint accel = globalMouseCoord - previousPosition; - accelAccumulator += QVector2D(accel); + if (y < windowRect.y()) { + if (yWrap) { + y += windowRect.height(); + } else { + y = (windowRect.y() + 2); + } + } else if (y > (windowRect.y() + windowRect.height())) { + if (yWrap){ + y -= windowRect.height(); + } else { + y = windowRect.y() + (windowRect.height() - 2); + } + } - if( accelAccumulator.length() > 10 ) { - accelAccumulator = accelAccumulator.normalized() * 10; + if (globalMouseCoord.x() != x || globalMouseCoord.y() != y) { + QCursor::setPos(x, y); + lastKnownPosition = QPoint(x, y) - accel; + + //Important -- teleportation needs to caught to prevent high-acceleration + //values from QCursor::setPos being read in this event. + catchTeleportCorrection = true; } + } +} - //Window-space wrapping for mouse dragging. Allows for blender-like - //infinite mouse scrolls. - QRect windowRect = window()->geometry(); - windowRect = kisGrowRect(windowRect, -2); - const bool xWrap = true; - const bool yWrap = true; - if (!windowRect.contains(globalMouseCoord)) { - int x = globalMouseCoord.x(); - int y = globalMouseCoord.y(); - - if (x < windowRect.x() ) { - if (xWrap) { - x += windowRect.width(); - } else { - x = (windowRect.x() + 2); - } - } else if (x > (windowRect.x() + windowRect.width()) ) { - if (xWrap) { - x -= windowRect.width(); - } else { - x = windowRect.x() + (windowRect.width() - 2); - } - } +void KisZoomableScrollbar::handleScroll(const QPoint &accel) +{ + const qreal sliderMovementPix = (orientation() == Qt::Horizontal) ? accel.x() : accel.y(); + const qreal zoomMovementPix = (orientation() == Qt::Horizontal) ? -accel.y() : -accel.x(); + const qreal documentLength = maximum() - minimum() + pageStep(); + const qreal widgetLength = (orientation() == Qt::Horizontal) ? width() : height(); + const qreal widgetThickness = (orientation() == Qt::Horizontal) ? height() : width(); - if (y < windowRect.y()) { - if (yWrap) { - y += windowRect.height(); - } else { - y = (windowRect.y() + 2); - } - } else if (y > (windowRect.y() + windowRect.height())) { - if (yWrap){ - y -= windowRect.height(); - } else { - y = windowRect.y() + (windowRect.height() - 2); - } - } + const QVector2D perpendicularDirection = (orientation() == Qt::Horizontal) ? QVector2D(0, 1) : QVector2D(1, 0); + const float perpendicularity = QVector2D::dotProduct(perpendicularDirection.normalized(), accelerationAccumulator.normalized()); - if (globalMouseCoord.x() != x || globalMouseCoord.y() != y) { - QCursor::setPos(x, y); - previousPosition = QPoint(x, y) - accel; + if (abs(perpendicularity) > zoomPerpendicularityThreshold && zoomMovementPix != 0) { + zoom(qreal(zoomMovementPix) / (widgetThickness * 5)); + } else if (sliderMovementPix != 0) { - //Important -- teleportation needs to caught to prevent high-acceleration - //values from QCursor::setPos being read in this event. - catchTeleportCorrection = true; - } - } else { - previousPosition = globalMouseCoord; + const int currentPosition = sliderPosition(); + scrollSubPixelAccumulator += (documentLength) * (sliderMovementPix / widgetLength); + + setSliderPosition(currentPosition + scrollSubPixelAccumulator); + if (currentPosition + scrollSubPixelAccumulator > maximum() || currentPosition + scrollSubPixelAccumulator < minimum() ) { + overscroll(scrollSubPixelAccumulator); } - const qreal sliderMovementPix = (orientation() == Qt::Horizontal) ? accel.x() : accel.y(); - const qreal zoomMovementPix = (orientation() == Qt::Horizontal) ? -accel.y() : -accel.x(); - const qreal documentLength = maximum() - minimum() + pageStep(); - const qreal widgetLength = (orientation() == Qt::Horizontal) ? width() : height(); - const qreal widgetThickness = (orientation() == Qt::Horizontal) ? height() : width(); + const int sign = (scrollSubPixelAccumulator > 0) - (scrollSubPixelAccumulator < 0); + scrollSubPixelAccumulator -= floor(abs(scrollSubPixelAccumulator)) * sign; + } +} - const QVector2D perpendicularDirection = (orientation() == Qt::Horizontal) ? QVector2D(0, 1) : QVector2D(1, 0); - const float perpendicularity = QVector2D::dotProduct(perpendicularDirection.normalized(), accelAccumulator.normalized()); +void KisZoomableScrollbar::tabletEvent(QTabletEvent *event) { + if ( event->type() == QTabletEvent::TabletMove && isSliderDown() ) { - if (abs(perpendicularity) > zoomDeadzone && zoomMovementPix != 0) { - zoom(qreal(zoomMovementPix) / (widgetThickness * 5)); - } else if (sliderMovementPix != 0) { + QPoint globalMouseCoord = mapToGlobal(event->pos()); + QPoint accel = globalMouseCoord - lastKnownPosition; + accelerationAccumulator += QVector2D(accel); - const int currentPosition = sliderPosition(); - scrollAccumulator += (documentLength) * (sliderMovementPix / widgetLength); + if( accelerationAccumulator.length() > 5) { + accelerationAccumulator = accelerationAccumulator.normalized() * 5; + } - setSliderPosition(currentPosition + scrollAccumulator); - if (currentPosition + scrollAccumulator > maximum() || currentPosition + scrollAccumulator < minimum() ) { - overscroll(scrollAccumulator); - } + handleScroll(accel); + lastKnownPosition = globalMouseCoord; + event->accept(); + } else { + + if (event->type() == QTabletEvent::TabletPress) { + QPoint globalMouseCoord = mapToGlobal(event->pos()); + lastKnownPosition = globalMouseCoord; + } + + QScrollBar::tabletEvent(event); + } +} + +void KisZoomableScrollbar::mousePressEvent(QMouseEvent *event) +{ + const bool wasSliderDownBefore = isSliderDown(); + QScrollBar::mousePressEvent(event); + + if( isSliderDown() && !wasSliderDownBefore ){ + lastKnownPosition = mapToGlobal(event->pos()); + accelerationAccumulator = QVector2D(0,0); + setCursor(Qt::BlankCursor); + } else { + setCursor(Qt::ArrowCursor); + } + +} - const int sign = (scrollAccumulator > 0) - (scrollAccumulator < 0); - scrollAccumulator -= floor(abs(scrollAccumulator)) * sign; +void KisZoomableScrollbar::mouseMoveEvent(QMouseEvent *event) +{ + if (isSliderDown()) { + + + QPoint globalMouseCoord = mapToGlobal(event->pos()); + QPoint accel = globalMouseCoord - lastKnownPosition; + accelerationAccumulator += QVector2D(accel); + + if (catchTeleports(event)){ + return; } + if( accelerationAccumulator.length() > 5 ) { + accelerationAccumulator = accelerationAccumulator.normalized() * 5; + } + handleScroll(accel); + lastKnownPosition = globalMouseCoord; + handleWrap(accel, globalMouseCoord); + event->accept(); } else { QScrollBar::mouseMoveEvent(event); } } void KisZoomableScrollbar::mouseReleaseEvent(QMouseEvent *event) { const QPoint cursorTranslationNormal = (orientation() == Qt::Horizontal) ? QPoint(1,0) : QPoint(0,1); const qreal widgetLength = (orientation() == Qt::Horizontal) ? width() : height(); const qreal documentLength = maximum() - minimum() + pageStep(); const qreal widgetLengthOffsetPix = (sliderPosition() / documentLength) * widgetLength; setCursor(Qt::ArrowCursor); QScrollBar::mouseReleaseEvent(event); } void KisZoomableScrollbar::wheelEvent(QWheelEvent *event) { const int delta = (event->angleDelta().y() / 8) * singleStep() * -1; const int currentPosition = sliderPosition(); if (currentPosition + delta > maximum() || currentPosition + delta < minimum()){ overscroll(delta); } QScrollBar::wheelEvent(event); } void KisZoomableScrollbar::setZoomDeadzone(float value) { - zoomDeadzone = value; + zoomPerpendicularityThreshold = value; } diff --git a/libs/widgetutils/kis_zoom_scrollbar.h b/libs/widgetutils/kis_zoom_scrollbar.h index 18b65ccaab..897e6c2e35 100644 --- a/libs/widgetutils/kis_zoom_scrollbar.h +++ b/libs/widgetutils/kis_zoom_scrollbar.h @@ -1,58 +1,69 @@ /* * Copyright (c) 2020 Eoin O'Neill * Copyright (c) 2020 Emmet O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ZOOM_SCROLLBAR_H #define KIS_ZOOM_SCROLLBAR_H #include #include #include class KRITAWIDGETUTILS_EXPORT KisZoomableScrollbar : public QScrollBar { Q_OBJECT private: - QPoint previousPosition; - QVector2D accelAccumulator; - qreal scrollAccumulator; - qreal zoomDeadzone; + QPoint lastKnownPosition; + QVector2D accelerationAccumulator; + qreal scrollSubPixelAccumulator; + qreal zoomPerpendicularityThreshold; bool catchTeleportCorrection = false; public: KisZoomableScrollbar(QWidget* parent = 0); KisZoomableScrollbar(Qt::Orientation orientation, QWidget * parent = 0); ~KisZoomableScrollbar(); + //Catch for teleportation from one side of the screen to the other. + bool catchTeleports(QMouseEvent* event); + + //Window-space wrapping for mouse dragging. Allows for blender-like + //infinite mouse scrolls. + void handleWrap(const QPoint &accel, const QPoint &globalMouseCoord); + + //Scroll based on a mouse acceleration value. + void handleScroll(const QPoint &accel); + + void tabletEvent(QTabletEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void wheelEvent(QWheelEvent *event) override; void setZoomDeadzone(float value); Q_SIGNALS: void zoom(qreal delta); void overscroll(int delta); }; #endif // KIS_ZOOM_SCROLLBAR_H