1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qapplication.h"
5#include "qdebug.h"
6#include "qeffects_p.h"
7#include "qelapsedtimer.h"
8#include "qevent.h"
9#include "qimage.h"
10#include "qpainter.h"
11#include "qscreen.h"
12#include "qpixmap.h"
13#include "qpointer.h"
14#include "qtimer.h"
15#include "qwidget.h"
16#include "private/qwidget_p.h"
17#include "qwindow.h"
18
19
20QT_BEGIN_NAMESPACE
21
22struct DeleteLater
23{
24 void operator()(QObject *o) const
25 {
26 if (o)
27 o->deleteLater();
28 }
29};
30
31/*
32 Internal class QAlphaWidget.
33
34 The QAlphaWidget is shown while the animation lasts
35 and displays the pixmap resulting from the alpha blending.
36*/
37
38class QAlphaWidget: public QWidget, private QEffects
39{
40 Q_OBJECT
41public:
42 QAlphaWidget(QWidget* w, Qt::WindowFlags f = { });
43 ~QAlphaWidget();
44
45 void run(int time);
46
47protected:
48 void paintEvent(QPaintEvent* e) override;
49 void closeEvent(QCloseEvent*) override;
50 void alphaBlend();
51 bool eventFilter(QObject *, QEvent *) override;
52
53protected slots:
54 void render();
55
56private:
57 QPixmap pm;
58 double alpha;
59 QImage backImage;
60 QImage frontImage;
61 QImage mixedImage;
62 QPointer<QWidget> widget;
63 int duration;
64 int elapsed;
65 bool showWidget;
66 QTimer anim;
67 QElapsedTimer checkTime;
68};
69
70static std::unique_ptr<QAlphaWidget, DeleteLater> q_blend;
71
72/*
73 Constructs a QAlphaWidget.
74*/
75QAlphaWidget::QAlphaWidget(QWidget* w, Qt::WindowFlags f)
76 : QWidget(nullptr, f)
77{
78 QWidgetPrivate::get(w: this)->setScreen(w->screen());
79#ifndef Q_OS_WIN
80 setEnabled(false);
81#endif
82 setAttribute(Qt::WA_NoSystemBackground, on: true);
83 widget = w;
84 alpha = 0;
85}
86
87QAlphaWidget::~QAlphaWidget()
88{
89#if defined(Q_OS_WIN)
90 // Restore user-defined opacity value
91 if (widget)
92 widget->setWindowOpacity(1);
93#endif
94}
95
96/*
97 \reimp
98*/
99void QAlphaWidget::paintEvent(QPaintEvent*)
100{
101 QPainter p(this);
102 p.drawPixmap(x: 0, y: 0, pm);
103}
104
105/*
106 Starts the alphablending animation.
107 The animation will take about \a time ms
108*/
109void QAlphaWidget::run(int time)
110{
111 duration = time;
112
113 if (duration < 0)
114 duration = 150;
115
116 if (!widget)
117 return;
118
119 elapsed = 0;
120 checkTime.start();
121
122 showWidget = true;
123#if defined(Q_OS_WIN)
124 qApp->installEventFilter(this);
125 widget->setWindowOpacity(0.0);
126 widget->show();
127 connect(&anim, &QTimer::timeout, this, &QAlphaWidget::render);
128 anim.start(1);
129#else
130 //This is roughly equivalent to calling setVisible(true) without actually showing the widget
131 widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: true);
132 widget->setAttribute(Qt::WA_WState_Hidden, on: false);
133
134 qApp->installEventFilter(filterObj: this);
135
136 move(ax: widget->geometry().x(),ay: widget->geometry().y());
137 resize(w: widget->size().width(), h: widget->size().height());
138
139 frontImage = widget->grab().toImage();
140 backImage = QGuiApplication::primaryScreen()->grabWindow(window: 0,
141 x: widget->geometry().x(), y: widget->geometry().y(),
142 w: widget->geometry().width(), h: widget->geometry().height()).toImage();
143
144 if (!backImage.isNull() && checkTime.elapsed() < duration / 2) {
145 mixedImage = backImage.copy();
146 pm = QPixmap::fromImage(image: mixedImage);
147 show();
148 setEnabled(false);
149
150 connect(sender: &anim, signal: &QTimer::timeout, context: this, slot: &QAlphaWidget::render);
151 anim.start(msec: 1);
152 } else {
153 duration = 0;
154 render();
155 }
156#endif
157}
158
159/*
160 \reimp
161*/
162bool QAlphaWidget::eventFilter(QObject *o, QEvent *e)
163{
164 switch (e->type()) {
165 case QEvent::Move:
166 if (o != widget)
167 break;
168 move(ax: widget->geometry().x(),ay: widget->geometry().y());
169 update();
170 break;
171 case QEvent::Hide:
172 case QEvent::Close:
173 if (o != widget)
174 break;
175 Q_FALLTHROUGH();
176 case QEvent::MouseButtonPress:
177 case QEvent::MouseButtonDblClick:
178 showWidget = false;
179 render();
180 break;
181 case QEvent::KeyPress: {
182#ifndef QT_NO_SHORTCUT
183 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
184 if (ke->matches(key: QKeySequence::Cancel)) {
185 showWidget = false;
186 } else
187#endif
188 {
189 duration = 0;
190 }
191 render();
192 break;
193 }
194 default:
195 break;
196 }
197 return QWidget::eventFilter(watched: o, event: e);
198}
199
200/*
201 \reimp
202*/
203void QAlphaWidget::closeEvent(QCloseEvent *e)
204{
205 e->accept();
206 if (!q_blend)
207 return;
208
209 showWidget = false;
210 render();
211
212 QWidget::closeEvent(event: e);
213}
214
215/*
216 Render alphablending for the time elapsed.
217
218 Show the blended widget and free all allocated source
219 if the blending is finished.
220*/
221void QAlphaWidget::render()
222{
223 int tempel = checkTime.elapsed();
224 if (elapsed >= tempel)
225 elapsed++;
226 else
227 elapsed = tempel;
228
229 if (duration != 0)
230 alpha = tempel / double(duration);
231 else
232 alpha = 1;
233
234#if defined(Q_OS_WIN)
235 if (alpha >= 1 || !showWidget) {
236 anim.stop();
237 qApp->removeEventFilter(this);
238 widget->setWindowOpacity(1);
239 q_blend.reset();
240 } else {
241 widget->setWindowOpacity(alpha);
242 }
243#else
244 if (alpha >= 1 || !showWidget) {
245 anim.stop();
246 qApp->removeEventFilter(obj: this);
247
248 if (widget) {
249 if (!showWidget) {
250 widget->hide();
251 } else {
252 //Since we are faking the visibility of the widget
253 //we need to unset the hidden state on it before calling show
254 widget->setAttribute(Qt::WA_WState_Hidden, on: true);
255 widget->show();
256 lower();
257 }
258 }
259 q_blend.reset();
260 } else {
261 alphaBlend();
262 pm = QPixmap::fromImage(image: mixedImage);
263 repaint();
264 }
265#endif // defined(Q_OS_WIN)
266}
267
268/*
269 Calculate an alphablended image.
270*/
271void QAlphaWidget::alphaBlend()
272{
273 const int a = qRound(d: alpha*256);
274 const int ia = 256 - a;
275
276 const int sw = frontImage.width();
277 const int sh = frontImage.height();
278 const qsizetype bpl = frontImage.bytesPerLine();
279 switch(frontImage.depth()) {
280 case 32:
281 {
282 uchar *mixed_data = mixedImage.bits();
283 const uchar *back_data = backImage.bits();
284 const uchar *front_data = frontImage.bits();
285
286 for (int sy = 0; sy < sh; sy++) {
287 quint32* mixed = (quint32*)mixed_data;
288 const quint32* back = (const quint32*)back_data;
289 const quint32* front = (const quint32*)front_data;
290 for (int sx = 0; sx < sw; sx++) {
291 quint32 bp = back[sx];
292 quint32 fp = front[sx];
293
294 mixed[sx] = qRgb(r: (qRed(rgb: bp)*ia + qRed(rgb: fp)*a)>>8,
295 g: (qGreen(rgb: bp)*ia + qGreen(rgb: fp)*a)>>8,
296 b: (qBlue(rgb: bp)*ia + qBlue(rgb: fp)*a)>>8);
297 }
298 mixed_data += bpl;
299 back_data += bpl;
300 front_data += bpl;
301 }
302 break;
303 }
304 default:
305 break;
306 }
307}
308
309/*
310 Internal class QRollEffect
311
312 The QRollEffect widget is shown while the animation lasts
313 and displays a scrolling pixmap.
314*/
315
316class QRollEffect : public QWidget, private QEffects
317{
318 Q_OBJECT
319public:
320 QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient);
321
322 void run(int time);
323
324protected:
325 void paintEvent(QPaintEvent*) override;
326 void closeEvent(QCloseEvent*) override;
327
328private slots:
329 void scroll();
330
331private:
332 QPointer<QWidget> widget;
333
334 int currentHeight;
335 int currentWidth;
336 int totalHeight;
337 int totalWidth;
338
339 int duration;
340 int elapsed;
341 bool done;
342 bool showWidget;
343 int orientation;
344
345 QTimer anim;
346 QElapsedTimer checkTime;
347
348 QPixmap pm;
349};
350
351static std::unique_ptr<QRollEffect, DeleteLater> q_roll;
352
353/*
354 Construct a QRollEffect widget.
355*/
356QRollEffect::QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient)
357 : QWidget(nullptr, f), orientation(orient)
358{
359 QWidgetPrivate::get(w: this)->setScreen(w->screen());
360#ifndef Q_OS_WIN
361 setEnabled(false);
362#endif
363
364 widget = w;
365 Q_ASSERT(widget);
366
367 setAttribute(Qt::WA_NoSystemBackground, on: true);
368
369 if (widget->testAttribute(attribute: Qt::WA_Resized)) {
370 totalWidth = widget->width();
371 totalHeight = widget->height();
372 } else {
373 totalWidth = widget->sizeHint().width();
374 totalHeight = widget->sizeHint().height();
375 }
376
377 currentHeight = totalHeight;
378 currentWidth = totalWidth;
379
380 if (orientation & (RightScroll|LeftScroll))
381 currentWidth = 0;
382 if (orientation & (DownScroll|UpScroll))
383 currentHeight = 0;
384
385 pm = widget->grab();
386}
387
388/*
389 \reimp
390*/
391void QRollEffect::paintEvent(QPaintEvent*)
392{
393 int x = orientation & RightScroll ? qMin(a: 0, b: currentWidth - totalWidth) : 0;
394 int y = orientation & DownScroll ? qMin(a: 0, b: currentHeight - totalHeight) : 0;
395
396 QPainter p(this);
397 p.drawPixmap(x, y, pm);
398}
399
400/*
401 \reimp
402*/
403void QRollEffect::closeEvent(QCloseEvent *e)
404{
405 e->accept();
406 if (done)
407 return;
408
409 showWidget = false;
410 done = true;
411 scroll();
412
413 QWidget::closeEvent(event: e);
414}
415
416/*
417 Start the animation.
418
419 The animation will take about \a time ms, or is
420 calculated if \a time is negative
421*/
422void QRollEffect::run(int time)
423{
424 if (!widget)
425 return;
426
427 duration = time;
428 elapsed = 0;
429
430 if (duration < 0) {
431 int dist = 0;
432 if (orientation & (RightScroll|LeftScroll))
433 dist += totalWidth - currentWidth;
434 if (orientation & (DownScroll|UpScroll))
435 dist += totalHeight - currentHeight;
436 duration = qMin(a: qMax(a: dist/3, b: 50), b: 120);
437 }
438
439 connect(sender: &anim, signal: &QTimer::timeout, context: this, slot: &QRollEffect::scroll);
440
441 move(ax: widget->geometry().x(),ay: widget->geometry().y());
442 resize(w: qMin(a: currentWidth, b: totalWidth), h: qMin(a: currentHeight, b: totalHeight));
443
444 //This is roughly equivalent to calling setVisible(true) without actually showing the widget
445 widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: true);
446 widget->setAttribute(Qt::WA_WState_Hidden, on: false);
447
448 show();
449 setEnabled(false);
450
451 showWidget = true;
452 done = false;
453 anim.start(msec: 1);
454 checkTime.start();
455}
456
457/*
458 Roll according to the time elapsed.
459*/
460void QRollEffect::scroll()
461{
462 if (!done && widget) {
463 int tempel = checkTime.elapsed();
464 if (elapsed >= tempel)
465 elapsed++;
466 else
467 elapsed = tempel;
468
469 if (currentWidth != totalWidth) {
470 currentWidth = totalWidth * (elapsed/duration)
471 + (2 * totalWidth * (elapsed%duration) + duration)
472 / (2 * duration);
473 // equiv. to int((totalWidth*elapsed) / duration + 0.5)
474 }
475 if (currentHeight != totalHeight) {
476 currentHeight = totalHeight * (elapsed/duration)
477 + (2 * totalHeight * (elapsed%duration) + duration)
478 / (2 * duration);
479 // equiv. to int((totalHeight*elapsed) / duration + 0.5)
480 }
481 done = (currentHeight >= totalHeight) &&
482 (currentWidth >= totalWidth);
483
484 int w = totalWidth;
485 int h = totalHeight;
486 int x = widget->geometry().x();
487 int y = widget->geometry().y();
488
489 if (orientation & RightScroll || orientation & LeftScroll)
490 w = qMin(a: currentWidth, b: totalWidth);
491 if (orientation & DownScroll || orientation & UpScroll)
492 h = qMin(a: currentHeight, b: totalHeight);
493
494 setUpdatesEnabled(false);
495 if (orientation & UpScroll)
496 y = widget->geometry().y() + qMax(a: 0, b: totalHeight - currentHeight);
497 if (orientation & LeftScroll)
498 x = widget->geometry().x() + qMax(a: 0, b: totalWidth - currentWidth);
499 if (orientation & UpScroll || orientation & LeftScroll)
500 move(ax: x, ay: y);
501
502 resize(w, h);
503 setUpdatesEnabled(true);
504 repaint();
505 }
506 if (done || !widget) {
507 anim.stop();
508 if (widget) {
509 if (!showWidget) {
510#ifdef Q_OS_WIN
511 setEnabled(true);
512 setFocus();
513#endif
514 widget->hide();
515 } else {
516 //Since we are faking the visibility of the widget
517 //we need to unset the hidden state on it before calling show
518 widget->setAttribute(Qt::WA_WState_Hidden, on: true);
519 widget->show();
520 lower();
521 }
522 }
523 q_roll.reset();
524 }
525}
526
527/*
528 Scroll widget \a w in \a time ms. \a orient may be 1 (vertical), 2
529 (horizontal) or 3 (diagonal).
530*/
531void qScrollEffect(QWidget* w, QEffects::DirFlags orient, int time)
532{
533 q_roll.reset();
534
535 if (!w)
536 return;
537
538 QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Move);
539 QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Resize);
540 Qt::WindowFlags flags = Qt::ToolTip;
541
542 // those can be popups - they would steal the focus, but are disabled
543 q_roll.reset(p: new QRollEffect(w, flags, orient));
544 q_roll->run(time);
545}
546
547/*
548 Fade in widget \a w in \a time ms.
549*/
550void qFadeEffect(QWidget* w, int time)
551{
552 q_blend.reset();
553
554 if (!w)
555 return;
556
557 QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Move);
558 QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Resize);
559
560 Qt::WindowFlags flags = Qt::ToolTip;
561
562 // those can be popups - they would steal the focus, but are disabled
563 q_blend.reset(p: new QAlphaWidget(w, flags));
564
565 q_blend->run(time);
566}
567
568QT_END_NAMESPACE
569
570/*
571 Delete this after timeout
572*/
573
574#include "qeffects.moc"
575

source code of qtbase/src/widgets/widgets/qeffects.cpp