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 }