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