1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "ui/layers/layer_widget.h"
8
9 #include "ui/layers/box_layer_widget.h"
10 #include "ui/widgets/shadow.h"
11 #include "ui/image/image_prepare.h"
12 #include "ui/ui_utility.h"
13 #include "ui/round_rect.h"
14 #include "styles/style_layers.h"
15 #include "styles/style_widgets.h"
16 #include "styles/palette.h"
17
18 #include <QtGui/QtEvents>
19
20 namespace Ui {
21
22 class LayerStackWidget::BackgroundWidget : public TWidget {
23 public:
24 explicit BackgroundWidget(QWidget *parent);
25
setDoneCallback(Fn<void ()> callback)26 void setDoneCallback(Fn<void()> callback) {
27 _doneCallback = std::move(callback);
28 }
29
30 void setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox);
31 void setCacheImages(
32 QPixmap &&bodyCache,
33 QPixmap &&mainMenuCache,
34 QPixmap &&specialLayerCache,
35 QPixmap &&layerCache);
36 void removeBodyCache();
37 [[nodiscard]] bool hasBodyCache() const;
38 void refreshBodyCache(QPixmap &&bodyCache);
39 void startAnimation(Action action);
40 void skipAnimation(Action action);
41 void finishAnimating();
42
animating() const43 bool animating() const {
44 return _a_mainMenuShown.animating() || _a_specialLayerShown.animating() || _a_layerShown.animating();
45 }
46
47 protected:
48 void paintEvent(QPaintEvent *e) override;
49
50 private:
isShown() const51 bool isShown() const {
52 return _mainMenuShown || _specialLayerShown || _layerShown;
53 }
54 void checkIfDone();
55 void setMainMenuShown(bool shown);
56 void setSpecialLayerShown(bool shown);
57 void setLayerShown(bool shown);
58 void checkWasShown(bool wasShown);
59 void animationCallback();
60
61 QPixmap _bodyCache;
62 QPixmap _mainMenuCache;
63 int _mainMenuCacheWidth = 0;
64 QPixmap _specialLayerCache;
65 QPixmap _layerCache;
66 RoundRect _roundRect;
67
68 Fn<void()> _doneCallback;
69
70 bool _wasAnimating = false;
71 bool _inPaintEvent = false;
72 Ui::Animations::Simple _a_shown;
73 Ui::Animations::Simple _a_mainMenuShown;
74 Ui::Animations::Simple _a_specialLayerShown;
75 Ui::Animations::Simple _a_layerShown;
76
77 QRect _specialLayerBox, _specialLayerCacheBox;
78 QRect _layerBox, _layerCacheBox;
79 int _mainMenuRight = 0;
80
81 bool _mainMenuShown = false;
82 bool _specialLayerShown = false;
83 bool _layerShown = false;
84
85 };
86
BackgroundWidget(QWidget * parent)87 LayerStackWidget::BackgroundWidget::BackgroundWidget(QWidget *parent)
88 : TWidget(parent)
89 , _roundRect(ImageRoundRadius::Small, st::boxBg) {
90 }
91
setCacheImages(QPixmap && bodyCache,QPixmap && mainMenuCache,QPixmap && specialLayerCache,QPixmap && layerCache)92 void LayerStackWidget::BackgroundWidget::setCacheImages(
93 QPixmap &&bodyCache,
94 QPixmap &&mainMenuCache,
95 QPixmap &&specialLayerCache,
96 QPixmap &&layerCache) {
97 _bodyCache = std::move(bodyCache);
98 _mainMenuCache = std::move(mainMenuCache);
99 _specialLayerCache = std::move(specialLayerCache);
100 _layerCache = std::move(layerCache);
101 _specialLayerCacheBox = _specialLayerBox;
102 _layerCacheBox = _layerBox;
103 setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
104 }
105
removeBodyCache()106 void LayerStackWidget::BackgroundWidget::removeBodyCache() {
107 if (hasBodyCache()) {
108 _bodyCache = {};
109 setAttribute(Qt::WA_OpaquePaintEvent, false);
110 }
111 }
112
hasBodyCache() const113 bool LayerStackWidget::BackgroundWidget::hasBodyCache() const {
114 return !_bodyCache.isNull();
115 }
116
refreshBodyCache(QPixmap && bodyCache)117 void LayerStackWidget::BackgroundWidget::refreshBodyCache(
118 QPixmap &&bodyCache) {
119 _bodyCache = std::move(bodyCache);
120 setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
121 }
122
startAnimation(Action action)123 void LayerStackWidget::BackgroundWidget::startAnimation(Action action) {
124 if (action == Action::ShowMainMenu) {
125 setMainMenuShown(true);
126 } else if (action != Action::HideLayer
127 && action != Action::HideSpecialLayer) {
128 setMainMenuShown(false);
129 }
130 if (action == Action::ShowSpecialLayer) {
131 setSpecialLayerShown(true);
132 } else if (action == Action::ShowMainMenu
133 || action == Action::HideAll
134 || action == Action::HideSpecialLayer) {
135 setSpecialLayerShown(false);
136 }
137 if (action == Action::ShowLayer) {
138 setLayerShown(true);
139 } else if (action != Action::ShowSpecialLayer
140 && action != Action::HideSpecialLayer) {
141 setLayerShown(false);
142 }
143 _wasAnimating = true;
144 checkIfDone();
145 }
146
skipAnimation(Action action)147 void LayerStackWidget::BackgroundWidget::skipAnimation(Action action) {
148 startAnimation(action);
149 finishAnimating();
150 }
151
checkIfDone()152 void LayerStackWidget::BackgroundWidget::checkIfDone() {
153 if (!_wasAnimating || _inPaintEvent || animating()) {
154 return;
155 }
156 _wasAnimating = false;
157 _mainMenuCache = _specialLayerCache = _layerCache = QPixmap();
158 removeBodyCache();
159 if (_doneCallback) {
160 _doneCallback();
161 }
162 }
163
setMainMenuShown(bool shown)164 void LayerStackWidget::BackgroundWidget::setMainMenuShown(bool shown) {
165 auto wasShown = isShown();
166 if (_mainMenuShown != shown) {
167 _mainMenuShown = shown;
168 _a_mainMenuShown.start([this] { animationCallback(); }, _mainMenuShown ? 0. : 1., _mainMenuShown ? 1. : 0., st::boxDuration, anim::easeOutCirc);
169 }
170 _mainMenuCacheWidth = (_mainMenuCache.width() / style::DevicePixelRatio())
171 - st::boxRoundShadow.extend.right();
172 _mainMenuRight = _mainMenuShown ? _mainMenuCacheWidth : 0;
173 checkWasShown(wasShown);
174 }
175
setSpecialLayerShown(bool shown)176 void LayerStackWidget::BackgroundWidget::setSpecialLayerShown(bool shown) {
177 auto wasShown = isShown();
178 if (_specialLayerShown != shown) {
179 _specialLayerShown = shown;
180 _a_specialLayerShown.start([this] { animationCallback(); }, _specialLayerShown ? 0. : 1., _specialLayerShown ? 1. : 0., st::boxDuration);
181 }
182 checkWasShown(wasShown);
183 }
184
setLayerShown(bool shown)185 void LayerStackWidget::BackgroundWidget::setLayerShown(bool shown) {
186 auto wasShown = isShown();
187 if (_layerShown != shown) {
188 _layerShown = shown;
189 _a_layerShown.start([this] { animationCallback(); }, _layerShown ? 0. : 1., _layerShown ? 1. : 0., st::boxDuration);
190 }
191 checkWasShown(wasShown);
192 }
193
checkWasShown(bool wasShown)194 void LayerStackWidget::BackgroundWidget::checkWasShown(bool wasShown) {
195 if (isShown() != wasShown) {
196 _a_shown.start([this] { animationCallback(); }, wasShown ? 1. : 0., wasShown ? 0. : 1., st::boxDuration, anim::easeOutCirc);
197 }
198 }
199
setLayerBoxes(const QRect & specialLayerBox,const QRect & layerBox)200 void LayerStackWidget::BackgroundWidget::setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox) {
201 _specialLayerBox = specialLayerBox;
202 _layerBox = layerBox;
203 update();
204 }
205
paintEvent(QPaintEvent * e)206 void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) {
207 Painter p(this);
208
209 _inPaintEvent = true;
210 auto guard = gsl::finally([this] {
211 _inPaintEvent = false;
212 crl::on_main(this, [=] { checkIfDone(); });
213 });
214
215 if (!_bodyCache.isNull()) {
216 p.drawPixmap(0, 0, _bodyCache);
217 }
218
219 auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox;
220 auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox;
221
222 auto mainMenuProgress = _a_mainMenuShown.value(-1);
223 auto mainMenuRight = (_mainMenuCache.isNull() || mainMenuProgress < 0) ? _mainMenuRight : (mainMenuProgress < 0) ? _mainMenuRight : anim::interpolate(0, _mainMenuCacheWidth, mainMenuProgress);
224 if (mainMenuRight) {
225 // Move showing boxes to the right while main menu is hiding.
226 if (!_specialLayerCache.isNull()) {
227 specialLayerBox.moveLeft(specialLayerBox.left() + mainMenuRight / 2);
228 }
229 if (!_layerCache.isNull()) {
230 layerBox.moveLeft(layerBox.left() + mainMenuRight / 2);
231 }
232 }
233 auto bgOpacity = _a_shown.value(isShown() ? 1. : 0.);
234 auto specialLayerOpacity = _a_specialLayerShown.value(_specialLayerShown ? 1. : 0.);
235 auto layerOpacity = _a_layerShown.value(_layerShown ? 1. : 0.);
236 if (bgOpacity == 0.) {
237 return;
238 }
239
240 p.setOpacity(bgOpacity);
241 auto overSpecialOpacity = (layerOpacity * specialLayerOpacity);
242 auto bg = myrtlrect(mainMenuRight, 0, width() - mainMenuRight, height());
243
244 if (_mainMenuCache.isNull() && mainMenuRight > 0) {
245 // All cache images are taken together with their shadows,
246 // so we paint shadow only when there is no cache.
247 Ui::Shadow::paint(p, myrtlrect(0, 0, mainMenuRight, height()), width(), st::boxRoundShadow, RectPart::Right);
248 }
249
250 if (_specialLayerCache.isNull() && !specialLayerBox.isEmpty()) {
251 // All cache images are taken together with their shadows,
252 // so we paint shadow only when there is no cache.
253 auto sides = RectPart::Left | RectPart::Right;
254 auto topCorners = (specialLayerBox.y() > 0);
255 auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height());
256 if (topCorners) {
257 sides |= RectPart::Top;
258 }
259 if (bottomCorners) {
260 sides |= RectPart::Bottom;
261 }
262 if (topCorners || bottomCorners) {
263 p.setClipRegion(QRegion(rect()) - specialLayerBox.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)) - specialLayerBox.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)));
264 }
265 Ui::Shadow::paint(p, specialLayerBox, width(), st::boxRoundShadow, sides);
266
267 if (topCorners || bottomCorners) {
268 // In case of painting the shadow above the special layer we get
269 // glitches in the corners, so we need to paint the corners once more.
270 p.setClipping(false);
271 auto parts = (topCorners ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
272 | (bottomCorners ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None);
273 _roundRect.paint(p, specialLayerBox, parts);
274 }
275 }
276
277 if (!layerBox.isEmpty() && !_specialLayerCache.isNull() && overSpecialOpacity < bgOpacity) {
278 // In case of moving special layer below the background while showing a box
279 // we need to fill special layer rect below its cache with a complex opacity
280 // (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches
281 // in the transparent special layer cache corners after filling special layer
282 // rect above its cache with alpha_current opacity.
283 const auto region = QRegion(bg) - specialLayerBox;
284 for (const auto &rect : region) {
285 p.fillRect(rect, st::layerBg);
286 }
287 p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF())));
288 p.fillRect(specialLayerBox, st::layerBg);
289 p.setOpacity(bgOpacity);
290 } else {
291 p.fillRect(bg, st::layerBg);
292 }
293
294 if (!_specialLayerCache.isNull() && specialLayerOpacity > 0) {
295 p.setOpacity(specialLayerOpacity);
296 auto cacheLeft = specialLayerBox.x() - st::boxRoundShadow.extend.left();
297 auto cacheTop = specialLayerBox.y() - (specialLayerBox.y() > 0 ? st::boxRoundShadow.extend.top() : 0);
298 p.drawPixmapLeft(cacheLeft, cacheTop, width(), _specialLayerCache);
299 }
300 if (!layerBox.isEmpty()) {
301 if (!_specialLayerCache.isNull()) {
302 p.setOpacity(overSpecialOpacity);
303 p.fillRect(specialLayerBox, st::layerBg);
304 }
305 if (_layerCache.isNull()) {
306 p.setOpacity(layerOpacity);
307 Ui::Shadow::paint(p, layerBox, width(), st::boxRoundShadow);
308 }
309 }
310 if (!_layerCache.isNull() && layerOpacity > 0) {
311 p.setOpacity(layerOpacity);
312 p.drawPixmapLeft(layerBox.topLeft() - QPoint(st::boxRoundShadow.extend.left(), st::boxRoundShadow.extend.top()), width(), _layerCache);
313 }
314 if (!_mainMenuCache.isNull() && mainMenuRight > 0) {
315 p.setOpacity(1.);
316 auto shownWidth = mainMenuRight + st::boxRoundShadow.extend.right();
317 auto sourceWidth = shownWidth * style::DevicePixelRatio();
318 auto sourceRect = style::rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width());
319 p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect);
320 }
321 }
322
finishAnimating()323 void LayerStackWidget::BackgroundWidget::finishAnimating() {
324 _a_shown.stop();
325 _a_mainMenuShown.stop();
326 _a_specialLayerShown.stop();
327 _a_layerShown.stop();
328 checkIfDone();
329 }
330
animationCallback()331 void LayerStackWidget::BackgroundWidget::animationCallback() {
332 update();
333 checkIfDone();
334 }
335
LayerStackWidget(QWidget * parent)336 LayerStackWidget::LayerStackWidget(QWidget *parent)
337 : RpWidget(parent)
338 , _background(this) {
339 setGeometry(parentWidget()->rect());
340 hide();
341 _background->setDoneCallback([this] { animationDone(); });
342 }
343
setInnerFocus()344 void LayerWidget::setInnerFocus() {
345 if (!isAncestorOf(window()->focusWidget())) {
346 doSetInnerFocus();
347 }
348 }
349
overlaps(const QRect & globalRect)350 bool LayerWidget::overlaps(const QRect &globalRect) {
351 if (isHidden()) {
352 return false;
353 }
354 auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size());
355 if (testAttribute(Qt::WA_OpaquePaintEvent)) {
356 return rect().contains(testRect);
357 }
358 if (QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius).contains(testRect)) {
359 return true;
360 }
361 if (QRect(st::boxRadius, 0, width() - 2 * st::boxRadius, height()).contains(testRect)) {
362 return true;
363 }
364 return false;
365 }
366
mousePressEvent(QMouseEvent * e)367 void LayerWidget::mousePressEvent(QMouseEvent *e) {
368 e->accept();
369 }
370
resizeEvent(QResizeEvent * e)371 void LayerWidget::resizeEvent(QResizeEvent *e) {
372 if (_resizedCallback) {
373 _resizedCallback();
374 }
375 }
376
setHideByBackgroundClick(bool hide)377 void LayerStackWidget::setHideByBackgroundClick(bool hide) {
378 _hideByBackgroundClick = hide;
379 }
380
keyPressEvent(QKeyEvent * e)381 void LayerStackWidget::keyPressEvent(QKeyEvent *e) {
382 if (e->key() == Qt::Key_Escape) {
383 hideCurrent(anim::type::normal);
384 }
385 }
386
mousePressEvent(QMouseEvent * e)387 void LayerStackWidget::mousePressEvent(QMouseEvent *e) {
388 Ui::PostponeCall(this, [=] { backgroundClicked(); });
389 }
390
backgroundClicked()391 void LayerStackWidget::backgroundClicked() {
392 if (!_hideByBackgroundClick) {
393 return;
394 }
395 if (const auto layer = currentLayer()) {
396 if (!layer->closeByOutsideClick()) {
397 return;
398 }
399 } else if (const auto special = _specialLayer.data()) {
400 if (!special->closeByOutsideClick()) {
401 return;
402 }
403 }
404 hideCurrent(anim::type::normal);
405 }
406
hideCurrent(anim::type animated)407 void LayerStackWidget::hideCurrent(anim::type animated) {
408 return currentLayer() ? hideLayers(animated) : hideAll(animated);
409 }
410
hideLayers(anim::type animated)411 void LayerStackWidget::hideLayers(anim::type animated) {
412 startAnimation([] {}, [&] {
413 clearLayers();
414 }, Action::HideLayer, animated);
415 }
416
hideAll(anim::type animated)417 void LayerStackWidget::hideAll(anim::type animated) {
418 startAnimation([] {}, [&] {
419 clearLayers();
420 clearSpecialLayer();
421 _mainMenu.destroy();
422 }, Action::HideAll, animated);
423 }
424
hideAllAnimatedPrepare()425 void LayerStackWidget::hideAllAnimatedPrepare() {
426 prepareAnimation([] {}, [&] {
427 clearLayers();
428 clearSpecialLayer();
429 _mainMenu.destroy();
430 }, Action::HideAll, anim::type::normal);
431 }
432
hideAllAnimatedRun()433 void LayerStackWidget::hideAllAnimatedRun() {
434 if (_background->hasBodyCache()) {
435 removeBodyCache();
436 hideChildren();
437 auto bodyCache = Ui::GrabWidget(parentWidget());
438 showChildren();
439 _background->refreshBodyCache(std::move(bodyCache));
440 }
441 _background->startAnimation(Action::HideAll);
442 }
443
hideTopLayer(anim::type animated)444 void LayerStackWidget::hideTopLayer(anim::type animated) {
445 if (_specialLayer || _mainMenu) {
446 hideLayers(animated);
447 } else {
448 hideAll(animated);
449 }
450 }
451
removeBodyCache()452 void LayerStackWidget::removeBodyCache() {
453 _background->removeBodyCache();
454 setAttribute(Qt::WA_OpaquePaintEvent, false);
455 }
456
layerShown() const457 bool LayerStackWidget::layerShown() const {
458 return _specialLayer || currentLayer() || _mainMenu;
459 }
460
topShownLayer() const461 const LayerWidget *LayerStackWidget::topShownLayer() const {
462 if (const auto result = currentLayer()) {
463 return result;
464 } else if (const auto special = _specialLayer.data()) {
465 return special;
466 } else if (const auto menu = _mainMenu.data()) {
467 return menu;
468 }
469 return nullptr;
470 }
471
setStyleOverrides(const style::Box * boxSt,const style::Box * layerSt)472 void LayerStackWidget::setStyleOverrides(
473 const style::Box *boxSt,
474 const style::Box *layerSt) {
475 _boxSt = boxSt;
476 _layerSt = layerSt;
477 }
478
setCacheImages()479 void LayerStackWidget::setCacheImages() {
480 auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
481 auto specialLayerCache = QPixmap();
482 if (_specialLayer) {
483 Ui::SendPendingMoveResizeEvents(_specialLayer);
484 auto sides = RectPart::Left | RectPart::Right;
485 if (_specialLayer->y() > 0) {
486 sides |= RectPart::Top;
487 }
488 if (_specialLayer->y() + _specialLayer->height() < height()) {
489 sides |= RectPart::Bottom;
490 }
491 specialLayerCache = Ui::Shadow::grab(_specialLayer, st::boxRoundShadow, sides);
492 }
493 auto layerCache = QPixmap();
494 if (auto layer = currentLayer()) {
495 layerCache = Ui::Shadow::grab(layer, st::boxRoundShadow);
496 }
497 if (isAncestorOf(window()->focusWidget())) {
498 setFocus();
499 }
500 if (_mainMenu) {
501 removeBodyCache();
502 hideChildren();
503 bodyCache = Ui::GrabWidget(parentWidget());
504 showChildren();
505 mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, RectPart::Right);
506 }
507 setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull());
508 updateLayerBoxes();
509 _background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache));
510 }
511
closeLayer(not_null<LayerWidget * > layer)512 void LayerStackWidget::closeLayer(not_null<LayerWidget*> layer) {
513 const auto weak = Ui::MakeWeak(layer.get());
514 if (Ui::InFocusChain(layer)) {
515 setFocus();
516 }
517 if (!layer->setClosing()) {
518 // This layer is already closing.
519 return;
520 } else if (!weak) {
521 // setClosing() could've killed the layer.
522 return;
523 }
524
525 if (layer == _specialLayer || layer == _mainMenu) {
526 hideAll(anim::type::normal);
527 } else if (layer == currentLayer()) {
528 if (_layers.size() == 1) {
529 hideCurrent(anim::type::normal);
530 } else {
531 const auto taken = std::move(_layers.back());
532 _layers.pop_back();
533
534 layer = currentLayer();
535 layer->parentResized();
536 if (!_background->animating()) {
537 layer->show();
538 showFinished();
539 }
540 }
541 } else {
542 for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) {
543 if (layer == i->get()) {
544 const auto taken = std::move(*i);
545 _layers.erase(i);
546 break;
547 }
548 }
549 }
550 }
551
updateLayerBoxes()552 void LayerStackWidget::updateLayerBoxes() {
553 const auto layerBox = [&] {
554 if (const auto layer = currentLayer()) {
555 return layer->geometry();
556 }
557 return QRect();
558 }();
559 const auto specialLayerBox = _specialLayer
560 ? _specialLayer->geometry()
561 : QRect();
562 _background->setLayerBoxes(specialLayerBox, layerBox);
563 update();
564 }
565
finishAnimating()566 void LayerStackWidget::finishAnimating() {
567 _background->finishAnimating();
568 }
569
canSetFocus() const570 bool LayerStackWidget::canSetFocus() const {
571 return (currentLayer() || _specialLayer || _mainMenu);
572 }
573
setInnerFocus()574 void LayerStackWidget::setInnerFocus() {
575 if (_background->animating()) {
576 setFocus();
577 } else if (auto l = currentLayer()) {
578 l->setInnerFocus();
579 } else if (_specialLayer) {
580 _specialLayer->setInnerFocus();
581 } else if (_mainMenu) {
582 _mainMenu->setInnerFocus();
583 }
584 }
585
contentOverlapped(const QRect & globalRect)586 bool LayerStackWidget::contentOverlapped(const QRect &globalRect) {
587 if (isHidden()) {
588 return false;
589 }
590 if (_specialLayer && _specialLayer->overlaps(globalRect)) {
591 return true;
592 }
593 if (auto layer = currentLayer()) {
594 return layer->overlaps(globalRect);
595 }
596 return false;
597 }
598
599 template <typename SetupNew, typename ClearOld>
prepareAnimation(SetupNew && setupNewWidgets,ClearOld && clearOldWidgets,Action action,anim::type animated)600 bool LayerStackWidget::prepareAnimation(
601 SetupNew &&setupNewWidgets,
602 ClearOld &&clearOldWidgets,
603 Action action,
604 anim::type animated) {
605 if (animated == anim::type::instant) {
606 setupNewWidgets();
607 clearOldWidgets();
608 prepareForAnimation();
609 _background->skipAnimation(action);
610 } else {
611 setupNewWidgets();
612 setCacheImages();
613 const auto weak = Ui::MakeWeak(this);
614 clearOldWidgets();
615 if (weak) {
616 prepareForAnimation();
617 return true;
618 }
619 }
620 return false;
621 }
622
623 template <typename SetupNew, typename ClearOld>
startAnimation(SetupNew && setupNewWidgets,ClearOld && clearOldWidgets,Action action,anim::type animated)624 void LayerStackWidget::startAnimation(
625 SetupNew &&setupNewWidgets,
626 ClearOld &&clearOldWidgets,
627 Action action,
628 anim::type animated) {
629 const auto alive = prepareAnimation(
630 std::forward<SetupNew>(setupNewWidgets),
631 std::forward<ClearOld>(clearOldWidgets),
632 action,
633 animated);
634 if (alive) {
635 _background->startAnimation(action);
636 }
637 }
638
resizeEvent(QResizeEvent * e)639 void LayerStackWidget::resizeEvent(QResizeEvent *e) {
640 const auto weak = Ui::MakeWeak(this);
641 _background->setGeometry(rect());
642 if (!weak) {
643 return;
644 }
645 if (_specialLayer) {
646 _specialLayer->parentResized();
647 if (!weak) {
648 return;
649 }
650 }
651 if (const auto layer = currentLayer()) {
652 layer->parentResized();
653 if (!weak) {
654 return;
655 }
656 }
657 if (_mainMenu) {
658 _mainMenu->parentResized();
659 if (!weak) {
660 return;
661 }
662 }
663 updateLayerBoxes();
664 }
665
prepareForAnimation()666 void LayerStackWidget::prepareForAnimation() {
667 if (isHidden()) {
668 show();
669 }
670 if (_mainMenu) {
671 if (Ui::InFocusChain(_mainMenu)) {
672 setFocus();
673 }
674 _mainMenu->hide();
675 }
676 if (_specialLayer) {
677 if (Ui::InFocusChain(_specialLayer)) {
678 setFocus();
679 }
680 _specialLayer->hide();
681 }
682 if (const auto layer = currentLayer()) {
683 if (Ui::InFocusChain(layer)) {
684 setFocus();
685 }
686 layer->hide();
687 }
688 }
689
animationDone()690 void LayerStackWidget::animationDone() {
691 bool hidden = true;
692 if (_mainMenu) {
693 _mainMenu->show();
694 hidden = false;
695 }
696 if (_specialLayer) {
697 _specialLayer->show();
698 hidden = false;
699 }
700 if (auto layer = currentLayer()) {
701 layer->show();
702 hidden = false;
703 }
704 setAttribute(Qt::WA_OpaquePaintEvent, false);
705 if (hidden) {
706 _hideFinishStream.fire({});
707 } else {
708 showFinished();
709 }
710 }
711
hideFinishEvents() const712 rpl::producer<> LayerStackWidget::hideFinishEvents() const {
713 return _hideFinishStream.events();
714 }
715
showFinished()716 void LayerStackWidget::showFinished() {
717 fixOrder();
718 sendFakeMouseEvent();
719 updateLayerBoxes();
720 if (_specialLayer) {
721 _specialLayer->showFinished();
722 }
723 if (auto layer = currentLayer()) {
724 layer->showFinished();
725 }
726 if (canSetFocus()) {
727 setInnerFocus();
728 }
729 }
730
showSpecialLayer(object_ptr<LayerWidget> layer,anim::type animated)731 void LayerStackWidget::showSpecialLayer(
732 object_ptr<LayerWidget> layer,
733 anim::type animated) {
734 startAnimation([&] {
735 _specialLayer.destroy();
736 _specialLayer = std::move(layer);
737 initChildLayer(_specialLayer);
738 }, [&] {
739 _mainMenu.destroy();
740 }, Action::ShowSpecialLayer, animated);
741 }
742
showSectionInternal(not_null<::Window::SectionMemento * > memento,const::Window::SectionShow & params)743 bool LayerStackWidget::showSectionInternal(
744 not_null<::Window::SectionMemento*> memento,
745 const ::Window::SectionShow ¶ms) {
746 if (_specialLayer) {
747 return _specialLayer->showSectionInternal(memento, params);
748 }
749 return false;
750 }
751
hideSpecialLayer(anim::type animated)752 void LayerStackWidget::hideSpecialLayer(anim::type animated) {
753 startAnimation([] {}, [&] {
754 clearSpecialLayer();
755 _mainMenu.destroy();
756 }, Action::HideSpecialLayer, animated);
757 }
758
showMainMenu(object_ptr<LayerWidget> layer,anim::type animated)759 void LayerStackWidget::showMainMenu(
760 object_ptr<LayerWidget> layer,
761 anim::type animated) {
762 startAnimation([&] {
763 _mainMenu = std::move(layer);
764 initChildLayer(_mainMenu);
765 _mainMenu->moveToLeft(0, 0);
766 }, [&] {
767 clearLayers();
768 _specialLayer.destroy();
769 }, Action::ShowMainMenu, animated);
770 }
771
showBox(object_ptr<BoxContent> box,LayerOptions options,anim::type animated)772 void LayerStackWidget::showBox(
773 object_ptr<BoxContent> box,
774 LayerOptions options,
775 anim::type animated) {
776 showLayer(
777 std::make_unique<BoxLayerWidget>(this, std::move(box)),
778 options,
779 animated);
780 }
781
showLayer(std::unique_ptr<LayerWidget> layer,LayerOptions options,anim::type animated)782 void LayerStackWidget::showLayer(
783 std::unique_ptr<LayerWidget> layer,
784 LayerOptions options,
785 anim::type animated) {
786 if (options & LayerOption::KeepOther) {
787 if (options & LayerOption::ShowAfterOther) {
788 prependLayer(std::move(layer), animated);
789 } else {
790 appendLayer(std::move(layer), animated);
791 }
792 } else {
793 replaceLayer(std::move(layer), animated);
794 }
795 }
796
pushLayer(std::unique_ptr<LayerWidget> layer,anim::type animated)797 LayerWidget *LayerStackWidget::pushLayer(
798 std::unique_ptr<LayerWidget> layer,
799 anim::type animated) {
800 const auto oldLayer = currentLayer();
801 if (oldLayer) {
802 if (Ui::InFocusChain(oldLayer)) {
803 setFocus();
804 }
805 oldLayer->hide();
806 }
807 _layers.push_back(std::move(layer));
808 const auto raw = _layers.back().get();
809 initChildLayer(raw);
810
811 if (_layers.size() > 1) {
812 if (!_background->animating()) {
813 raw->setVisible(true);
814 showFinished();
815 }
816 } else {
817 startAnimation([] {}, [&] {
818 _mainMenu.destroy();
819 }, Action::ShowLayer, animated);
820 }
821
822 return raw;
823 }
824
appendLayer(std::unique_ptr<LayerWidget> layer,anim::type animated)825 void LayerStackWidget::appendLayer(
826 std::unique_ptr<LayerWidget> layer,
827 anim::type animated) {
828 pushLayer(std::move(layer), animated);
829 }
830
prependLayer(std::unique_ptr<LayerWidget> layer,anim::type animated)831 void LayerStackWidget::prependLayer(
832 std::unique_ptr<LayerWidget> layer,
833 anim::type animated) {
834 if (_layers.empty()) {
835 replaceLayer(std::move(layer), animated);
836 return;
837 }
838 _layers.insert(
839 begin(_layers),
840 std::move(layer));
841 const auto raw = _layers.front().get();
842 raw->hide();
843 initChildLayer(raw);
844 }
845
replaceLayer(std::unique_ptr<LayerWidget> layer,anim::type animated)846 void LayerStackWidget::replaceLayer(
847 std::unique_ptr<LayerWidget> layer,
848 anim::type animated) {
849 const auto pointer = pushLayer(std::move(layer), animated);
850 const auto removeTill = ranges::find(
851 _layers,
852 pointer,
853 &std::unique_ptr<LayerWidget>::get);
854 _closingLayers.insert(
855 end(_closingLayers),
856 std::make_move_iterator(begin(_layers)),
857 std::make_move_iterator(removeTill));
858 _layers.erase(begin(_layers), removeTill);
859 clearClosingLayers();
860 }
861
takeToThirdSection()862 bool LayerStackWidget::takeToThirdSection() {
863 return _specialLayer
864 ? _specialLayer->takeToThirdSection()
865 : false;
866 }
867
clearLayers()868 void LayerStackWidget::clearLayers() {
869 _closingLayers.insert(
870 end(_closingLayers),
871 std::make_move_iterator(begin(_layers)),
872 std::make_move_iterator(end(_layers)));
873 _layers.clear();
874 clearClosingLayers();
875 }
876
clearClosingLayers()877 void LayerStackWidget::clearClosingLayers() {
878 const auto weak = Ui::MakeWeak(this);
879 while (!_closingLayers.empty()) {
880 const auto index = _closingLayers.size() - 1;
881 const auto layer = _closingLayers.back().get();
882 if (Ui::InFocusChain(layer)) {
883 setFocus();
884 }
885
886 // This may destroy LayerStackWidget (by calling Ui::hideLayer).
887 // So each time we check a weak pointer (if we are still alive).
888 layer->setClosing();
889
890 // setClosing() could destroy 'this' or could call clearLayers().
891 if (weak && !_closingLayers.empty()) {
892 // We could enqueue more closing layers, so we remove by index.
893 Assert(index < _closingLayers.size());
894 Assert(_closingLayers[index].get() == layer);
895 _closingLayers.erase(begin(_closingLayers) + index);
896 } else {
897 // Everything was destroyed in clearLayers or ~LayerStackWidget.
898 break;
899 }
900 }
901 }
902
clearSpecialLayer()903 void LayerStackWidget::clearSpecialLayer() {
904 if (_specialLayer) {
905 _specialLayer->setClosing();
906 _specialLayer.destroy();
907 }
908 }
909
initChildLayer(LayerWidget * layer)910 void LayerStackWidget::initChildLayer(LayerWidget *layer) {
911 layer->setParent(this);
912 layer->setClosedCallback([=] { closeLayer(layer); });
913 layer->setResizedCallback([=] { updateLayerBoxes(); });
914 Ui::SendPendingMoveResizeEvents(layer);
915 layer->parentResized();
916 }
917
fixOrder()918 void LayerStackWidget::fixOrder() {
919 if (const auto layer = currentLayer()) {
920 _background->raise();
921 layer->raise();
922 } else if (_specialLayer) {
923 _specialLayer->raise();
924 }
925 if (_mainMenu) {
926 _mainMenu->raise();
927 }
928 }
929
sendFakeMouseEvent()930 void LayerStackWidget::sendFakeMouseEvent() {
931 SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
932 }
933
~LayerStackWidget()934 LayerStackWidget::~LayerStackWidget() {
935 // Some layer destructors call back into LayerStackWidget.
936 while (!_layers.empty() || !_closingLayers.empty()) {
937 hideAll(anim::type::instant);
938 clearClosingLayers();
939 }
940 }
941
942 } // namespace Ui
943