File indexing completed on 2025-04-27 04:37:42
0001 /* 0002 SPDX-FileCopyrightText: 2007-2008 Urs Wolfer <uwolfer@kde.org> 0003 Parts of this file have been take from okular: 0004 SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "floatingtoolbar.h" 0010 #include "krdc_debug.h" 0011 0012 #include <QApplication> 0013 #include <QBitmap> 0014 #include <QMouseEvent> 0015 #include <QPainter> 0016 #include <QStyle> 0017 #include <QTimer> 0018 0019 static const int actionIconSize = 22; 0020 static const int toolBarRBMargin = 2; 0021 static const double toolBarOpacity = 0.8; 0022 static const int visiblePixelWhenAutoHidden = 6; 0023 static const int autoHideTimeout = 500; 0024 static const int initialAutoHideTimeout = 2000; 0025 0026 /** 0027 * Denotes the various states of the animation. 0028 */ 0029 enum AnimState { Hiding, Showing, Still }; 0030 0031 class FloatingToolBarPrivate 0032 { 0033 public: 0034 FloatingToolBarPrivate(FloatingToolBar *qq) 0035 : q(qq) 0036 , anchorSide(FloatingToolBar::Left) 0037 , offsetPlaceHolder(new QWidget(qq)) 0038 , animState(Still) 0039 , toDelete(false) 0040 , visible(false) 0041 , sticky(false) 0042 , opacity(toolBarOpacity) 0043 // set queuedShow to true so we show the toolbar if we get a resize event on the anchorWidget 0044 , queuedShow(true) 0045 { 0046 } 0047 0048 // rebuild contents and reposition then widget 0049 void buildToolBar(); 0050 void reposition(); 0051 // compute the visible and hidden positions along current side 0052 QPoint getInnerPoint() const; 0053 QPoint getOuterPoint() const; 0054 0055 FloatingToolBar *q; 0056 0057 QWidget *anchorWidget; 0058 FloatingToolBar::Side anchorSide; 0059 QWidget *offsetPlaceHolder; 0060 0061 QTimer *animTimer; 0062 QTimer *autoHideTimer; 0063 QPoint currentPosition; 0064 QPoint endPosition; 0065 AnimState animState; 0066 bool toDelete; 0067 bool visible; 0068 bool sticky; 0069 qreal opacity; 0070 bool queuedShow; 0071 0072 QPixmap backgroundPixmap; 0073 }; 0074 0075 FloatingToolBar::FloatingToolBar(QWidget *parent, QWidget *anchorWidget) 0076 : QToolBar(parent) 0077 , d(new FloatingToolBarPrivate(this)) 0078 { 0079 ; 0080 addWidget(d->offsetPlaceHolder); 0081 0082 setMouseTracking(true); 0083 setIconSize(QSize(actionIconSize, actionIconSize)); 0084 d->anchorWidget = anchorWidget; 0085 0086 d->animTimer = new QTimer(this); 0087 connect(d->animTimer, SIGNAL(timeout()), this, SLOT(animate())); 0088 0089 d->autoHideTimer = new QTimer(this); 0090 connect(d->autoHideTimer, SIGNAL(timeout()), this, SLOT(hide())); 0091 0092 // apply a filter to get notified when anchor changes geometry 0093 d->anchorWidget->installEventFilter(this); 0094 } 0095 0096 FloatingToolBar::~FloatingToolBar() 0097 { 0098 delete d; 0099 } 0100 0101 void FloatingToolBar::addAction(QAction *action) 0102 { 0103 QToolBar::addAction(action); 0104 0105 // rebuild toolbar shape and contents only if the toolbar is already visible, 0106 // otherwise it will be done in showAndAnimate() 0107 if (isVisible()) 0108 d->reposition(); 0109 } 0110 0111 void FloatingToolBar::setSide(Side side) 0112 { 0113 d->anchorSide = side; 0114 0115 if (isVisible()) 0116 d->reposition(); 0117 } 0118 0119 void FloatingToolBar::setSticky(bool sticky) 0120 { 0121 d->sticky = sticky; 0122 0123 if (sticky) 0124 d->autoHideTimer->stop(); 0125 } 0126 0127 void FloatingToolBar::showAndAnimate() 0128 { 0129 if (d->animState == Showing) 0130 return; 0131 0132 d->animState = Showing; 0133 0134 show(); 0135 0136 // force update for case when toolbar has not been built yet 0137 d->reposition(); 0138 0139 // start scrolling in 0140 d->animTimer->start(20); 0141 0142 // This permits to show the toolbar for a while when going full screen. 0143 if (!d->sticky) 0144 d->autoHideTimer->start(initialAutoHideTimeout); 0145 } 0146 0147 void FloatingToolBar::hideAndDestroy() 0148 { 0149 if (d->animState == Hiding) 0150 return; 0151 0152 // set parameters for sliding out 0153 d->animState = Hiding; 0154 d->toDelete = true; 0155 d->endPosition = d->getOuterPoint(); 0156 0157 // start scrolling out 0158 d->animTimer->start(20); 0159 } 0160 0161 void FloatingToolBar::hide() 0162 { 0163 if (underMouse()) 0164 return; 0165 0166 if (d->visible) { 0167 QPoint diff; 0168 switch (d->anchorSide) { 0169 case Left: 0170 diff = QPoint(visiblePixelWhenAutoHidden, 0); 0171 break; 0172 case Right: 0173 diff = QPoint(-visiblePixelWhenAutoHidden, 0); 0174 break; 0175 case Top: 0176 diff = QPoint(0, visiblePixelWhenAutoHidden); 0177 break; 0178 case Bottom: 0179 diff = QPoint(0, -visiblePixelWhenAutoHidden); 0180 break; 0181 } 0182 d->animState = Hiding; 0183 d->endPosition = d->getOuterPoint() + diff; 0184 0185 // start scrolling out 0186 d->animTimer->start(20); 0187 } 0188 } 0189 0190 bool FloatingToolBar::eventFilter(QObject *obj, QEvent *e) 0191 { 0192 if (obj == d->anchorWidget && e->type() == QEvent::Resize) { 0193 if (d->queuedShow) { // if the toolbar is not visible yet, try to show it if the anchor widget is in fullscreen already 0194 d->queuedShow = false; 0195 showAndAnimate(); 0196 return true; 0197 } 0198 0199 // if anchorWidget changed geometry reposition toolbar 0200 d->animTimer->stop(); 0201 if ((d->animState == Hiding || !d->visible) && d->toDelete) 0202 deleteLater(); 0203 else 0204 d->reposition(); 0205 } 0206 0207 return QToolBar::eventFilter(obj, e); 0208 } 0209 0210 void FloatingToolBar::paintEvent(QPaintEvent *e) 0211 { 0212 QToolBar::paintEvent(e); 0213 0214 // paint the internal pixmap over the widget 0215 QPainter p(this); 0216 p.setOpacity(d->opacity); 0217 p.drawImage(e->rect().topLeft(), d->backgroundPixmap.toImage(), e->rect()); 0218 } 0219 0220 void FloatingToolBar::mousePressEvent(QMouseEvent *e) 0221 { 0222 if (e->button() == Qt::LeftButton) 0223 setCursor(Qt::SizeAllCursor); 0224 0225 QToolBar::mousePressEvent(e); 0226 } 0227 0228 void FloatingToolBar::mouseMoveEvent(QMouseEvent *e) 0229 { 0230 // show the toolbar again when it is auto-hidden 0231 if (!d->visible) { 0232 showAndAnimate(); 0233 return; 0234 } 0235 0236 if ((QApplication::mouseButtons() & Qt::LeftButton) != Qt::LeftButton) 0237 return; 0238 0239 // compute the nearest side to attach the widget to 0240 const QPoint parentPos = mapToParent(e->pos()); 0241 const float nX = (float)parentPos.x() / (float)d->anchorWidget->width(); 0242 const float nY = (float)parentPos.y() / (float)d->anchorWidget->height(); 0243 if (nX > 0.3 && nX < 0.7 && nY > 0.3 && nY < 0.7) 0244 return; 0245 bool LT = nX < (1.0 - nY); 0246 bool LB = nX < (nY); 0247 Side side = LT ? (LB ? Left : Top) : (LB ? Bottom : Right); 0248 0249 // check if side changed 0250 if (side == d->anchorSide) 0251 return; 0252 0253 d->anchorSide = side; 0254 d->reposition(); 0255 Q_EMIT orientationChanged((int)side); 0256 0257 QToolBar::mouseMoveEvent(e); 0258 } 0259 0260 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0261 void FloatingToolBar::enterEvent(QEnterEvent *e) 0262 #else 0263 void FloatingToolBar::enterEvent(QEvent *e) 0264 #endif 0265 { 0266 // Stop the autohide timer while the mouse is inside 0267 d->autoHideTimer->stop(); 0268 0269 if (!d->visible) 0270 showAndAnimate(); 0271 QToolBar::enterEvent(e); 0272 } 0273 0274 void FloatingToolBar::leaveEvent(QEvent *e) 0275 { 0276 if (!d->sticky) 0277 d->autoHideTimer->start(autoHideTimeout); 0278 QToolBar::leaveEvent(e); 0279 } 0280 0281 void FloatingToolBar::mouseReleaseEvent(QMouseEvent *e) 0282 { 0283 if (e->button() == Qt::LeftButton) 0284 setCursor(Qt::ArrowCursor); 0285 0286 QToolBar::mouseReleaseEvent(e); 0287 } 0288 0289 void FloatingToolBar::wheelEvent(QWheelEvent *e) 0290 { 0291 e->accept(); 0292 0293 const qreal diff = e->angleDelta().y() / 100.0 / 15.0; 0294 // qCDebug(KRDC) << diff; 0295 if (((d->opacity <= 1) && (diff > 0)) || ((d->opacity >= 0) && (diff < 0))) 0296 d->opacity += diff; 0297 0298 update(); 0299 0300 QToolBar::wheelEvent(e); 0301 } 0302 0303 void FloatingToolBarPrivate::buildToolBar() 0304 { 0305 const bool prevUpdates = q->updatesEnabled(); 0306 q->setUpdatesEnabled(false); 0307 0308 // 1. init numbers we are going to use 0309 const bool topLeft = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Top; 0310 const bool vertical = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Right; 0311 0312 if (vertical) { 0313 offsetPlaceHolder->setFixedSize(1, 7); 0314 q->setOrientation(Qt::Vertical); 0315 } else { 0316 offsetPlaceHolder->setFixedSize(7, 1); 0317 q->setOrientation(Qt::Horizontal); 0318 } 0319 0320 // 2. compute widget size 0321 const int myWidth = q->sizeHint().width() - 1; 0322 const int myHeight = q->sizeHint().height() - 1; 0323 0324 // 3. resize pixmap, mask and widget 0325 QBitmap mask(myWidth + 1, myHeight + 1); 0326 backgroundPixmap = QPixmap(myWidth + 1, myHeight + 1); 0327 backgroundPixmap.fill(Qt::transparent); 0328 0329 q->resize(myWidth + 1, myHeight + 1); 0330 0331 // 4. create and set transparency mask 0332 QPainter maskPainter(&mask); 0333 mask.fill(Qt::white); 0334 maskPainter.setBrush(Qt::black); 0335 if (vertical) 0336 maskPainter.drawRoundedRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight, Qt::RelativeSize); 0337 else 0338 maskPainter.drawRoundedRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10), Qt::RelativeSize); 0339 maskPainter.end(); 0340 q->setMask(mask); 0341 0342 // 5. draw background 0343 QPainter bufferPainter(&backgroundPixmap); 0344 bufferPainter.translate(0.5, 0.5); 0345 QPalette pal = q->palette(); 0346 // 5.1. draw horizontal/vertical gradient 0347 QLinearGradient grad; 0348 switch (anchorSide) { 0349 case FloatingToolBar::Left: 0350 grad = QLinearGradient(0, 1, myWidth + 1, 1); 0351 break; 0352 case FloatingToolBar::Right: 0353 grad = QLinearGradient(myWidth + 1, 1, 0, 1); 0354 break; 0355 case FloatingToolBar::Top: 0356 grad = QLinearGradient(1, 0, 1, myHeight + 1); 0357 break; 0358 case FloatingToolBar::Bottom: 0359 grad = QLinearGradient(1, myHeight + 1, 0, 1); 0360 break; 0361 } 0362 grad.setColorAt(0, pal.color(QPalette::Active, QPalette::Button)); 0363 grad.setColorAt(1, pal.color(QPalette::Active, QPalette::Light)); 0364 bufferPainter.setBrush(QBrush(grad)); 0365 // 5.2. draw rounded border 0366 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Dark).lighter(40)); 0367 bufferPainter.setRenderHints(QPainter::Antialiasing); 0368 if (vertical) 0369 bufferPainter.drawRoundedRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight, Qt::RelativeSize); 0370 else 0371 bufferPainter.drawRoundedRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10), Qt::RelativeSize); 0372 // 5.3. draw handle 0373 bufferPainter.translate(-0.5, -0.5); 0374 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Mid)); 0375 if (vertical) { 0376 int dx = anchorSide == FloatingToolBar::Left ? 2 : 4; 0377 bufferPainter.drawLine(dx, 6, dx + myWidth - 8, 6); 0378 bufferPainter.drawLine(dx, 9, dx + myWidth - 8, 9); 0379 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light)); 0380 bufferPainter.drawLine(dx + 1, 7, dx + myWidth - 7, 7); 0381 bufferPainter.drawLine(dx + 1, 10, dx + myWidth - 7, 10); 0382 } else { 0383 int dy = anchorSide == FloatingToolBar::Top ? 2 : 4; 0384 bufferPainter.drawLine(6, dy, 6, dy + myHeight - 8); 0385 bufferPainter.drawLine(9, dy, 9, dy + myHeight - 8); 0386 bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light)); 0387 bufferPainter.drawLine(7, dy + 1, 7, dy + myHeight - 7); 0388 bufferPainter.drawLine(10, dy + 1, 10, dy + myHeight - 7); 0389 } 0390 0391 q->setUpdatesEnabled(prevUpdates); 0392 } 0393 0394 void FloatingToolBarPrivate::reposition() 0395 { 0396 // note: hiding widget here will gives better gfx, but ends drag operation 0397 // rebuild widget and move it to its final place 0398 buildToolBar(); 0399 if (!visible) { 0400 currentPosition = getOuterPoint(); 0401 endPosition = getInnerPoint(); 0402 } else { 0403 currentPosition = getInnerPoint(); 0404 endPosition = getOuterPoint(); 0405 } 0406 q->move(currentPosition); 0407 } 0408 0409 QPoint FloatingToolBarPrivate::getInnerPoint() const 0410 { 0411 // returns the final position of the widget 0412 if (anchorSide == FloatingToolBar::Left) 0413 return QPoint(0, (anchorWidget->height() - q->height()) / 2); 0414 if (anchorSide == FloatingToolBar::Top) 0415 return QPoint((anchorWidget->width() - q->width()) / 2, 0); 0416 if (anchorSide == FloatingToolBar::Right) 0417 return QPoint(anchorWidget->width() - q->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2); 0418 return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() - q->height() + toolBarRBMargin); 0419 } 0420 0421 QPoint FloatingToolBarPrivate::getOuterPoint() const 0422 { 0423 // returns the point from which the transition starts 0424 if (anchorSide == FloatingToolBar::Left) 0425 return QPoint(-q->width(), (anchorWidget->height() - q->height()) / 2); 0426 if (anchorSide == FloatingToolBar::Top) 0427 return QPoint((anchorWidget->width() - q->width()) / 2, -q->height()); 0428 if (anchorSide == FloatingToolBar::Right) 0429 return QPoint(anchorWidget->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2); 0430 return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() + toolBarRBMargin); 0431 } 0432 0433 void FloatingToolBar::animate() 0434 { 0435 if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) { 0436 // move currentPosition towards endPosition 0437 int dX = d->endPosition.x() - d->currentPosition.x(); 0438 int dY = d->endPosition.y() - d->currentPosition.y(); 0439 dX = dX / 6 + qMax(-1, qMin(1, dX)); 0440 dY = dY / 6 + qMax(-1, qMin(1, dY)); 0441 d->currentPosition.setX(d->currentPosition.x() + dX); 0442 d->currentPosition.setY(d->currentPosition.y() + dY); 0443 } else { 0444 d->currentPosition = d->endPosition; 0445 } 0446 0447 move(d->currentPosition); 0448 0449 // handle arrival to the end 0450 if (d->currentPosition == d->endPosition) { 0451 d->animTimer->stop(); 0452 switch (d->animState) { 0453 case Hiding: 0454 d->visible = false; 0455 d->animState = Still; 0456 if (d->toDelete) 0457 deleteLater(); 0458 break; 0459 case Showing: 0460 d->visible = true; 0461 d->animState = Still; 0462 break; 0463 default: 0464 qCDebug(KRDC) << "Illegal state"; 0465 } 0466 } 0467 }