File indexing completed on 2024-12-22 04:13:15

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Eoin O 'Neill <eoinoneill1991@gmail.com>
0003  *  SPDX-FileCopyrightText: 2020 Emmet O 'Neill <emmetoneill.pdx@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 #include "kis_zoom_scrollbar.h"
0008 
0009 #include "kis_config.h"
0010 #include "kis_global.h"
0011 #include "kis_debug.h"
0012 #include "kis_tool_utils.h"
0013 #include <QMouseEvent>
0014 #include <QTabletEvent>
0015 
0016 KisZoomableScrollBar::KisZoomableScrollBar(QWidget *parent)
0017     : QScrollBar(parent)
0018     , lastKnownPosition(0,0)
0019     , accelerationAccumulator(0,0)
0020     , scrollSubPixelAccumulator(0.0f)
0021     , zoomThreshold(0.75f)
0022     , wheelOverscrollSensitivity(1.0f)
0023     , catchTeleportCorrection(false)
0024 {
0025     KisConfig config(true);
0026     zoomEnabled = config.scrollbarZoomEnabled();
0027 }
0028 
0029 KisZoomableScrollBar::KisZoomableScrollBar(Qt::Orientation orientation, QWidget *parent)
0030     : KisZoomableScrollBar(parent)
0031 {
0032     setOrientation(orientation);
0033 }
0034 
0035 KisZoomableScrollBar::~KisZoomableScrollBar()
0036 {
0037 }
0038 
0039 QPoint KisZoomableScrollBar::barPosition()
0040 {
0041     float barPositionNormalized = (float)(value() - minimum()) / (float)(maximum() + pageStep() - minimum());
0042     QPoint barPosition = orientation() == Qt::Horizontal ?
0043                 QPoint(barPositionNormalized * width() * devicePixelRatio(), 0) :
0044                 QPoint(0, barPositionNormalized * height() * devicePixelRatio());
0045 
0046     return mapToGlobal(QPoint(0,0)) + barPosition;
0047 }
0048 
0049 bool KisZoomableScrollBar::catchTeleports(QMouseEvent *event) {
0050     if (catchTeleportCorrection) {
0051         catchTeleportCorrection = false;
0052         event->accept();
0053         return true;
0054     }
0055 
0056     return false;
0057 }
0058 
0059 void KisZoomableScrollBar::handleWrap( const QPoint &accel, const QPoint &mouseCoord)
0060 {
0061     QRect windowRect = window()->geometry();
0062     windowRect = kisGrowRect(windowRect, -2);
0063     const int windowWidth = windowRect.width();
0064     const int windowHeight = windowRect.height();
0065     const int windowX = windowRect.x();
0066     const int windowY = windowRect.y();
0067     const bool xWrap = true;
0068     const bool yWrap = true;
0069 
0070     if (!windowRect.contains(mouseCoord)) {
0071         int x = mouseCoord.x();
0072         int y = mouseCoord.y();
0073 
0074         if (x < windowX && xWrap ) {
0075             x += (windowWidth - 2);
0076         } else if (x > (windowX + windowWidth) && xWrap ) {
0077             x -= (windowWidth - 2);
0078         }
0079 
0080         if (y < windowY && yWrap) {
0081             y += (windowHeight - 2);
0082         } else if (y > (windowY + windowHeight) && yWrap) {
0083             y -= (windowHeight - 2);
0084         }
0085 
0086         KisToolUtils::setCursorPos(QPoint(x, y));
0087         lastKnownPosition = QPoint(x, y) - accel;
0088 
0089         //Important -- teleportation needs to caught to prevent high-acceleration
0090         //values from QCursor::setPos being read in this event.
0091         catchTeleportCorrection = true;
0092     }
0093 }
0094 
0095 void KisZoomableScrollBar::handleScroll(const QPoint &accel)
0096 {
0097     const qreal sliderMovementPix = (orientation() == Qt::Horizontal) ? accel.x() * devicePixelRatio() : accel.y() * devicePixelRatio();
0098     const qreal zoomMovementPix = (orientation() == Qt::Horizontal) ? -accel.y() : -accel.x();
0099     const qreal documentLength = maximum() - minimum() + pageStep();
0100     const qreal widgetLength = (orientation() == Qt::Horizontal) ? width() * devicePixelRatio() : height() * devicePixelRatio();
0101     const qreal widgetThickness = (orientation() == Qt::Horizontal) ? height() * devicePixelRatio() : width() * devicePixelRatio();
0102 
0103     const QVector2D perpendicularDirection = (orientation() == Qt::Horizontal) ? QVector2D(0, 1) : QVector2D(1, 0);
0104     const float perpendicularity = QVector2D::dotProduct(perpendicularDirection.normalized(), accelerationAccumulator.normalized());
0105 
0106     if (zoomEnabled && qAbs(perpendicularity) > zoomThreshold && zoomMovementPix != 0) {
0107         zoom(qreal(zoomMovementPix) / qreal(widgetThickness * 2));
0108     } else if (sliderMovementPix != 0) {
0109         const int currentPosition = sliderPosition();
0110         scrollSubPixelAccumulator += (documentLength) * (sliderMovementPix / widgetLength);
0111 
0112         setSliderPosition(currentPosition + scrollSubPixelAccumulator);
0113         if (currentPosition + scrollSubPixelAccumulator > maximum() ||
0114             currentPosition + scrollSubPixelAccumulator <  minimum()) {
0115             overscroll(scrollSubPixelAccumulator);
0116         }
0117 
0118         const int sign = (scrollSubPixelAccumulator > 0) - (scrollSubPixelAccumulator < 0);
0119         scrollSubPixelAccumulator -= floor(abs(scrollSubPixelAccumulator)) * sign;
0120     }
0121 }
0122 
0123 void KisZoomableScrollBar::tabletEvent(QTabletEvent *event) {
0124     if ( event->type() == QTabletEvent::TabletMove && isSliderDown() ) {
0125         QPoint globalMouseCoord = mapToGlobal(event->pos());
0126         QPoint accel = globalMouseCoord - lastKnownPosition;
0127         accelerationAccumulator += QVector2D(accel);
0128 
0129         if( accelerationAccumulator.length() > 5) {
0130             accelerationAccumulator = accelerationAccumulator.normalized();
0131         }
0132 
0133         handleScroll(accel);
0134         lastKnownPosition = globalMouseCoord;
0135         event->accept();
0136     } else {
0137         if (event->type() == QTabletEvent::TabletPress) {
0138             QPoint globalMouseCoord = mapToGlobal(event->pos());
0139             lastKnownPosition = globalMouseCoord;
0140             setSliderDown(true);
0141             event->accept();
0142         } else if(event->type() == QTabletEvent::TabletRelease) {
0143             setSliderDown(false);
0144             event->accept();
0145         } else {
0146             QScrollBar::tabletEvent(event);
0147         }
0148     }
0149 }
0150 
0151 void KisZoomableScrollBar::mousePressEvent(QMouseEvent *event)
0152 {
0153     QScrollBar::mousePressEvent(event);
0154 
0155     lastKnownPosition = mapToGlobal(event->pos());
0156     accelerationAccumulator = QVector2D(0,0);
0157     QPoint worldPosition = mapToGlobal(event->pos());
0158     QPoint barPosition = this->barPosition();
0159     initialPositionRelativeToBar = worldPosition - barPosition;
0160     setCursor(Qt::BlankCursor);
0161 }
0162 
0163 
0164 void KisZoomableScrollBar::mouseMoveEvent(QMouseEvent *event)
0165 {
0166     QPoint globalMouseCoord = mapToGlobal(event->pos());
0167 
0168     QPoint accel = globalMouseCoord - lastKnownPosition;
0169     accelerationAccumulator += QVector2D(accel);
0170 
0171     if (catchTeleports(event)) {
0172         return;
0173     }
0174 
0175     if (accelerationAccumulator.length() > 5) {
0176         accelerationAccumulator = accelerationAccumulator.normalized();
0177     }
0178 
0179     handleScroll(accel);
0180     lastKnownPosition = globalMouseCoord;
0181     handleWrap(accel, mapToGlobal(event->pos()));
0182     event->accept();
0183 }
0184 
0185 void KisZoomableScrollBar::mouseReleaseEvent(QMouseEvent *event)
0186 {
0187     //If there's nowhere for our slider to  go, we should
0188     //still emit the slider release value.
0189     if (maximum() == minimum()) {
0190         emit sliderReleased();
0191     }
0192     const QPoint maximumCoordinates = mapToGlobal(QPoint(width() * devicePixelRatio(), height() * devicePixelRatio()));
0193     const QPoint minimumCoordinates = mapToGlobal(QPoint(0,0));
0194     const QPoint desiredCoordinates = barPosition() + initialPositionRelativeToBar;
0195     QPoint cursorPosition = QPoint(
0196                 qMax(minimumCoordinates.x(), qMin(maximumCoordinates.x(), desiredCoordinates.x())),
0197                 qMax(minimumCoordinates.y(), qMin(maximumCoordinates.y(), desiredCoordinates.y()))
0198                 );
0199     KisToolUtils::setCursorPos(cursorPosition);
0200     setCursor(Qt::ArrowCursor);
0201     QScrollBar::mouseReleaseEvent(event);
0202 }
0203 
0204 void KisZoomableScrollBar::wheelEvent(QWheelEvent *event) {
0205     const int delta = (event->angleDelta().y() / 8) * singleStep() * -1;
0206     const int currentPosition = sliderPosition();
0207 
0208     if (currentPosition + delta > maximum() || currentPosition + delta < minimum()){
0209         overscroll(delta * wheelOverscrollSensitivity);
0210     }
0211 
0212     QScrollBar::wheelEvent(event);
0213 }
0214 
0215 void KisZoomableScrollBar::setZoomDeadzone(float value)
0216 {
0217     zoomThreshold = value;
0218 }
0219 
0220 void KisZoomableScrollBar::setWheelOverscrollSensitivity(float sensitivity)
0221 {
0222     wheelOverscrollSensitivity = sensitivity;
0223 }