In some rare cases opening main menu was leading to some widgets showing "over" the background darkening filling. The glitch disappeared on the next repaint, so we trigger this repaint explicitly. The case it was reproducing with 100% rate: in TDesktop with disabled interface animations after showing and hiding of bot keyboard the message input field (widget with OpaquePaintEvent) was painted on top of the backround dark filling below main menu.
955 lines
25 KiB
C++
955 lines
25 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/layers/layer_widget.h"
|
|
|
|
#include "ui/cached_special_layer_shadow_corners.h"
|
|
#include "ui/layers/box_layer_widget.h"
|
|
#include "ui/widgets/shadow.h"
|
|
#include "ui/image/image_prepare.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "ui/round_rect.h"
|
|
#include "base/qt/qt_tab_key.h"
|
|
#include "base/integration.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_widgets.h"
|
|
#include "styles/palette.h"
|
|
|
|
#include <QtGui/QtEvents>
|
|
|
|
namespace Ui {
|
|
|
|
class LayerStackWidget::BackgroundWidget : public TWidget {
|
|
public:
|
|
using TWidget::TWidget;
|
|
|
|
void setDoneCallback(Fn<void()> callback) {
|
|
_doneCallback = std::move(callback);
|
|
}
|
|
|
|
void setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox);
|
|
void setCacheImages(
|
|
QPixmap &&bodyCache,
|
|
QPixmap &&mainMenuCache,
|
|
QPixmap &&specialLayerCache,
|
|
QPixmap &&layerCache);
|
|
void removeBodyCache();
|
|
[[nodiscard]] bool hasBodyCache() const;
|
|
void refreshBodyCache(QPixmap &&bodyCache);
|
|
void startAnimation(Action action);
|
|
void skipAnimation(Action action);
|
|
void finishAnimating();
|
|
|
|
bool animating() const {
|
|
return _a_mainMenuShown.animating() || _a_specialLayerShown.animating() || _a_layerShown.animating();
|
|
}
|
|
|
|
protected:
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
private:
|
|
bool isShown() const {
|
|
return _mainMenuShown || _specialLayerShown || _layerShown;
|
|
}
|
|
void checkIfDone();
|
|
void setMainMenuShown(bool shown);
|
|
void setSpecialLayerShown(bool shown);
|
|
void setLayerShown(bool shown);
|
|
void checkWasShown(bool wasShown);
|
|
void animationCallback();
|
|
|
|
QPixmap _bodyCache;
|
|
QPixmap _mainMenuCache;
|
|
int _mainMenuCacheWidth = 0;
|
|
QPixmap _specialLayerCache;
|
|
QPixmap _layerCache;
|
|
|
|
Fn<void()> _doneCallback;
|
|
|
|
bool _wasAnimating = false;
|
|
bool _inPaintEvent = false;
|
|
bool _repaintIssued = false;
|
|
Ui::Animations::Simple _a_shown;
|
|
Ui::Animations::Simple _a_mainMenuShown;
|
|
Ui::Animations::Simple _a_specialLayerShown;
|
|
Ui::Animations::Simple _a_layerShown;
|
|
|
|
QRect _specialLayerBox, _specialLayerCacheBox;
|
|
QRect _layerBox, _layerCacheBox;
|
|
int _mainMenuRight = 0;
|
|
|
|
bool _mainMenuShown = false;
|
|
bool _specialLayerShown = false;
|
|
bool _layerShown = false;
|
|
|
|
};
|
|
|
|
void LayerStackWidget::BackgroundWidget::setCacheImages(
|
|
QPixmap &&bodyCache,
|
|
QPixmap &&mainMenuCache,
|
|
QPixmap &&specialLayerCache,
|
|
QPixmap &&layerCache) {
|
|
_bodyCache = std::move(bodyCache);
|
|
_mainMenuCache = std::move(mainMenuCache);
|
|
_specialLayerCache = std::move(specialLayerCache);
|
|
_layerCache = std::move(layerCache);
|
|
_specialLayerCacheBox = _specialLayerBox;
|
|
_layerCacheBox = _layerBox;
|
|
_repaintIssued = false;
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::removeBodyCache() {
|
|
if (hasBodyCache()) {
|
|
_bodyCache = {};
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
}
|
|
}
|
|
|
|
bool LayerStackWidget::BackgroundWidget::hasBodyCache() const {
|
|
return !_bodyCache.isNull();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::refreshBodyCache(
|
|
QPixmap &&bodyCache) {
|
|
_bodyCache = std::move(bodyCache);
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::startAnimation(Action action) {
|
|
if (action == Action::ShowMainMenu) {
|
|
setMainMenuShown(true);
|
|
} else if (action != Action::HideLayer
|
|
&& action != Action::HideSpecialLayer) {
|
|
setMainMenuShown(false);
|
|
}
|
|
if (action == Action::ShowSpecialLayer) {
|
|
setSpecialLayerShown(true);
|
|
} else if (action == Action::ShowMainMenu
|
|
|| action == Action::HideAll
|
|
|| action == Action::HideSpecialLayer) {
|
|
setSpecialLayerShown(false);
|
|
}
|
|
if (action == Action::ShowLayer) {
|
|
setLayerShown(true);
|
|
} else if (action != Action::ShowSpecialLayer
|
|
&& action != Action::HideSpecialLayer) {
|
|
setLayerShown(false);
|
|
}
|
|
_wasAnimating = true;
|
|
checkIfDone();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::skipAnimation(Action action) {
|
|
_repaintIssued = false;
|
|
startAnimation(action);
|
|
finishAnimating();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::checkIfDone() {
|
|
if (!_wasAnimating || _inPaintEvent || animating()) {
|
|
return;
|
|
}
|
|
_wasAnimating = false;
|
|
_mainMenuCache = _specialLayerCache = _layerCache = QPixmap();
|
|
removeBodyCache();
|
|
if (_doneCallback) {
|
|
_doneCallback();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::setMainMenuShown(bool shown) {
|
|
auto wasShown = isShown();
|
|
if (_mainMenuShown != shown) {
|
|
_mainMenuShown = shown;
|
|
_a_mainMenuShown.start([this] { animationCallback(); }, _mainMenuShown ? 0. : 1., _mainMenuShown ? 1. : 0., st::boxDuration, anim::easeOutCirc);
|
|
}
|
|
_mainMenuCacheWidth = (_mainMenuCache.width() / style::DevicePixelRatio())
|
|
- st::boxRoundShadow.extend.right();
|
|
_mainMenuRight = _mainMenuShown ? _mainMenuCacheWidth : 0;
|
|
checkWasShown(wasShown);
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::setSpecialLayerShown(bool shown) {
|
|
auto wasShown = isShown();
|
|
if (_specialLayerShown != shown) {
|
|
_specialLayerShown = shown;
|
|
_a_specialLayerShown.start([this] { animationCallback(); }, _specialLayerShown ? 0. : 1., _specialLayerShown ? 1. : 0., st::boxDuration);
|
|
}
|
|
checkWasShown(wasShown);
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::setLayerShown(bool shown) {
|
|
auto wasShown = isShown();
|
|
if (_layerShown != shown) {
|
|
_layerShown = shown;
|
|
_a_layerShown.start([this] { animationCallback(); }, _layerShown ? 0. : 1., _layerShown ? 1. : 0., st::boxDuration);
|
|
}
|
|
checkWasShown(wasShown);
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::checkWasShown(bool wasShown) {
|
|
if (isShown() != wasShown) {
|
|
_a_shown.start([this] { animationCallback(); }, wasShown ? 1. : 0., wasShown ? 0. : 1., st::boxDuration, anim::easeOutCirc);
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox) {
|
|
_specialLayerBox = specialLayerBox;
|
|
_layerBox = layerBox;
|
|
update();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
_inPaintEvent = true;
|
|
auto guard = gsl::finally([this] {
|
|
_inPaintEvent = false;
|
|
crl::on_main(this, [=] { checkIfDone(); });
|
|
});
|
|
|
|
if (!_bodyCache.isNull()) {
|
|
p.drawPixmap(0, 0, _bodyCache);
|
|
}
|
|
|
|
auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox;
|
|
auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox;
|
|
|
|
auto mainMenuProgress = _a_mainMenuShown.value(-1);
|
|
auto mainMenuRight = (_mainMenuCache.isNull() || mainMenuProgress < 0) ? _mainMenuRight : (mainMenuProgress < 0) ? _mainMenuRight : anim::interpolate(0, _mainMenuCacheWidth, mainMenuProgress);
|
|
if (mainMenuRight) {
|
|
// Move showing boxes to the right while main menu is hiding.
|
|
if (!_specialLayerCache.isNull()) {
|
|
specialLayerBox.moveLeft(specialLayerBox.left() + mainMenuRight / 2);
|
|
}
|
|
if (!_layerCache.isNull()) {
|
|
layerBox.moveLeft(layerBox.left() + mainMenuRight / 2);
|
|
}
|
|
}
|
|
auto bgOpacity = _a_shown.value(isShown() ? 1. : 0.);
|
|
auto specialLayerOpacity = _a_specialLayerShown.value(_specialLayerShown ? 1. : 0.);
|
|
auto layerOpacity = _a_layerShown.value(_layerShown ? 1. : 0.);
|
|
if (bgOpacity == 0.) {
|
|
return;
|
|
}
|
|
|
|
p.setOpacity(bgOpacity);
|
|
auto overSpecialOpacity = (layerOpacity * specialLayerOpacity);
|
|
auto bg = myrtlrect(mainMenuRight, 0, width() - mainMenuRight, height());
|
|
|
|
if (_mainMenuCache.isNull() && mainMenuRight > 0) {
|
|
// All cache images are taken together with their shadows,
|
|
// so we paint shadow only when there is no cache.
|
|
Ui::Shadow::paint(p, myrtlrect(0, 0, mainMenuRight, height()), width(), st::boxRoundShadow, RectPart::Right);
|
|
}
|
|
|
|
if (_specialLayerCache.isNull() && !specialLayerBox.isEmpty()) {
|
|
// All cache images are taken together with their shadows,
|
|
// so we paint shadow only when there is no cache.
|
|
auto sides = RectPart::Left | RectPart::Right;
|
|
auto topCorners = (specialLayerBox.y() > 0);
|
|
auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height());
|
|
if (topCorners) {
|
|
sides |= RectPart::Top;
|
|
}
|
|
if (bottomCorners) {
|
|
sides |= RectPart::Bottom;
|
|
}
|
|
if (topCorners || bottomCorners) {
|
|
p.setClipRegion(QRegion(rect()) - specialLayerBox.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)) - specialLayerBox.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)));
|
|
}
|
|
Ui::Shadow::paint(p, specialLayerBox, width(), st::boxRoundShadow, Ui::SpecialLayerShadowCorners(), sides);
|
|
if (topCorners || bottomCorners) {
|
|
p.setClipping(false);
|
|
}
|
|
}
|
|
|
|
if (!layerBox.isEmpty() && !_specialLayerCache.isNull() && overSpecialOpacity < bgOpacity) {
|
|
// In case of moving special layer below the background while showing a box
|
|
// we need to fill special layer rect below its cache with a complex opacity
|
|
// (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches
|
|
// in the transparent special layer cache corners after filling special layer
|
|
// rect above its cache with alpha_current opacity.
|
|
const auto region = QRegion(bg) - specialLayerBox;
|
|
for (const auto &rect : region) {
|
|
p.fillRect(rect, st::layerBg);
|
|
}
|
|
p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF())));
|
|
p.fillRect(specialLayerBox, st::layerBg);
|
|
p.setOpacity(bgOpacity);
|
|
} else {
|
|
p.fillRect(bg, st::layerBg);
|
|
}
|
|
|
|
if (!_specialLayerCache.isNull() && specialLayerOpacity > 0) {
|
|
p.setOpacity(specialLayerOpacity);
|
|
auto cacheLeft = specialLayerBox.x() - st::boxRoundShadow.extend.left();
|
|
auto cacheTop = specialLayerBox.y() - (specialLayerBox.y() > 0 ? st::boxRoundShadow.extend.top() : 0);
|
|
p.drawPixmapLeft(cacheLeft, cacheTop, width(), _specialLayerCache);
|
|
}
|
|
if (!layerBox.isEmpty()) {
|
|
if (!_specialLayerCache.isNull()) {
|
|
p.setOpacity(overSpecialOpacity);
|
|
p.fillRect(specialLayerBox, st::layerBg);
|
|
}
|
|
if (_layerCache.isNull()) {
|
|
p.setOpacity(layerOpacity);
|
|
Ui::Shadow::paint(p, layerBox, width(), st::boxRoundShadow);
|
|
}
|
|
}
|
|
if (!_layerCache.isNull() && layerOpacity > 0) {
|
|
p.setOpacity(layerOpacity);
|
|
p.drawPixmapLeft(layerBox.topLeft() - QPoint(st::boxRoundShadow.extend.left(), st::boxRoundShadow.extend.top()), width(), _layerCache);
|
|
}
|
|
if (!_mainMenuCache.isNull() && mainMenuRight > 0) {
|
|
p.setOpacity(1.);
|
|
auto shownWidth = mainMenuRight + st::boxRoundShadow.extend.right();
|
|
auto sourceWidth = shownWidth * style::DevicePixelRatio();
|
|
auto sourceRect = style::rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width());
|
|
p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect);
|
|
}
|
|
if (!_repaintIssued && !_a_shown.animating()) {
|
|
_repaintIssued = true;
|
|
update();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::finishAnimating() {
|
|
_a_shown.stop();
|
|
_a_mainMenuShown.stop();
|
|
_a_specialLayerShown.stop();
|
|
_a_layerShown.stop();
|
|
checkIfDone();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::animationCallback() {
|
|
update();
|
|
checkIfDone();
|
|
}
|
|
|
|
LayerStackWidget::LayerStackWidget(QWidget *parent, ShowFactory showFactory)
|
|
: RpWidget(parent)
|
|
, _background(this)
|
|
, _showFactory(std::move(showFactory)) {
|
|
setGeometry(parentWidget()->rect());
|
|
hide();
|
|
_background->setDoneCallback([this] { animationDone(); });
|
|
}
|
|
|
|
void LayerWidget::setInnerFocus() {
|
|
if (!isAncestorOf(window()->focusWidget())) {
|
|
doSetInnerFocus();
|
|
}
|
|
}
|
|
|
|
bool LayerWidget::overlaps(const QRect &globalRect) {
|
|
if (isHidden()) {
|
|
return false;
|
|
}
|
|
auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size());
|
|
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
|
|
return rect().contains(testRect);
|
|
}
|
|
if (QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius).contains(testRect)) {
|
|
return true;
|
|
}
|
|
if (QRect(st::boxRadius, 0, width() - 2 * st::boxRadius, height()).contains(testRect)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LayerWidget::mousePressEvent(QMouseEvent *e) {
|
|
e->accept();
|
|
}
|
|
|
|
void LayerWidget::resizeEvent(QResizeEvent *e) {
|
|
if (_resizedCallback) {
|
|
_resizedCallback();
|
|
}
|
|
}
|
|
|
|
bool LayerWidget::focusNextPrevChild(bool next) {
|
|
return base::FocusNextPrevChildBlocked(this, next);
|
|
}
|
|
|
|
void LayerStackWidget::setHideByBackgroundClick(bool hide) {
|
|
_hideByBackgroundClick = hide;
|
|
}
|
|
|
|
void LayerStackWidget::keyPressEvent(QKeyEvent *e) {
|
|
if (e->key() == Qt::Key_Escape) {
|
|
hideCurrent(anim::type::normal);
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::mousePressEvent(QMouseEvent *e) {
|
|
Ui::PostponeCall(this, [=] { backgroundClicked(); });
|
|
}
|
|
|
|
void LayerStackWidget::backgroundClicked() {
|
|
if (!_hideByBackgroundClick) {
|
|
return;
|
|
}
|
|
if (const auto layer = currentLayer()) {
|
|
if (!layer->closeByOutsideClick()) {
|
|
return;
|
|
}
|
|
} else if (const auto special = _specialLayer.data()) {
|
|
if (!special->closeByOutsideClick()) {
|
|
return;
|
|
}
|
|
}
|
|
hideCurrent(anim::type::normal);
|
|
}
|
|
|
|
void LayerStackWidget::hideCurrent(anim::type animated) {
|
|
return currentLayer() ? hideLayers(animated) : hideAll(animated);
|
|
}
|
|
|
|
void LayerStackWidget::hideLayers(anim::type animated) {
|
|
startAnimation([] {}, [&] {
|
|
clearLayers();
|
|
}, Action::HideLayer, animated);
|
|
}
|
|
|
|
void LayerStackWidget::hideAll(anim::type animated) {
|
|
startAnimation([] {}, [&] {
|
|
clearLayers();
|
|
clearSpecialLayer();
|
|
_mainMenu.destroy();
|
|
}, Action::HideAll, animated);
|
|
}
|
|
|
|
void LayerStackWidget::hideAllAnimatedPrepare() {
|
|
prepareAnimation([] {}, [&] {
|
|
clearLayers();
|
|
clearSpecialLayer();
|
|
_mainMenu.destroy();
|
|
}, Action::HideAll, anim::type::normal);
|
|
}
|
|
|
|
void LayerStackWidget::hideAllAnimatedRun() {
|
|
if (_background->hasBodyCache()) {
|
|
removeBodyCache();
|
|
hideChildren();
|
|
auto bodyCache = Ui::GrabWidget(parentWidget());
|
|
showChildren();
|
|
_background->refreshBodyCache(std::move(bodyCache));
|
|
}
|
|
_background->startAnimation(Action::HideAll);
|
|
}
|
|
|
|
void LayerStackWidget::hideTopLayer(anim::type animated) {
|
|
if (_specialLayer || _mainMenu) {
|
|
hideLayers(animated);
|
|
} else {
|
|
hideAll(animated);
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::removeBodyCache() {
|
|
_background->removeBodyCache();
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
}
|
|
|
|
bool LayerStackWidget::layerShown() const {
|
|
return _specialLayer || currentLayer() || _mainMenu;
|
|
}
|
|
|
|
const LayerWidget *LayerStackWidget::topShownLayer() const {
|
|
if (const auto result = currentLayer()) {
|
|
return result;
|
|
} else if (const auto special = _specialLayer.data()) {
|
|
return special;
|
|
} else if (const auto menu = _mainMenu.data()) {
|
|
return menu;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void LayerStackWidget::setStyleOverrides(
|
|
const style::Box *boxSt,
|
|
const style::Box *layerSt) {
|
|
_boxSt = boxSt;
|
|
_layerSt = layerSt;
|
|
}
|
|
|
|
void LayerStackWidget::setCacheImages() {
|
|
auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
|
|
auto specialLayerCache = QPixmap();
|
|
if (_specialLayer) {
|
|
Ui::SendPendingMoveResizeEvents(_specialLayer);
|
|
auto sides = RectPart::Left | RectPart::Right;
|
|
if (_specialLayer->y() > 0) {
|
|
sides |= RectPart::Top;
|
|
}
|
|
if (_specialLayer->y() + _specialLayer->height() < height()) {
|
|
sides |= RectPart::Bottom;
|
|
}
|
|
specialLayerCache = Ui::Shadow::grab(_specialLayer, st::boxRoundShadow, sides);
|
|
}
|
|
auto layerCache = QPixmap();
|
|
if (auto layer = currentLayer()) {
|
|
layerCache = Ui::Shadow::grab(layer, st::boxRoundShadow);
|
|
}
|
|
if (isAncestorOf(window()->focusWidget())) {
|
|
setFocus();
|
|
}
|
|
if (_mainMenu) {
|
|
removeBodyCache();
|
|
hideChildren();
|
|
bodyCache = Ui::GrabWidget(parentWidget());
|
|
showChildren();
|
|
mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, RectPart::Right);
|
|
}
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull());
|
|
updateLayerBoxes();
|
|
_background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache));
|
|
}
|
|
|
|
void LayerStackWidget::closeLayer(not_null<LayerWidget*> layer) {
|
|
const auto weak = Ui::MakeWeak(layer.get());
|
|
if (Ui::InFocusChain(layer)) {
|
|
setFocus();
|
|
}
|
|
if (!layer->setClosing()) {
|
|
// This layer is already closing.
|
|
return;
|
|
} else if (!weak) {
|
|
// setClosing() could've killed the layer.
|
|
return;
|
|
}
|
|
|
|
if (layer == _specialLayer || layer == _mainMenu) {
|
|
hideAll(anim::type::normal);
|
|
} else if (layer == currentLayer()) {
|
|
if (_layers.size() == 1) {
|
|
hideCurrent(anim::type::normal);
|
|
} else {
|
|
const auto taken = std::move(_layers.back());
|
|
_layers.pop_back();
|
|
|
|
layer = currentLayer();
|
|
layer->parentResized();
|
|
if (!_background->animating()) {
|
|
layer->show();
|
|
showFinished();
|
|
}
|
|
}
|
|
} else {
|
|
for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) {
|
|
if (layer == i->get()) {
|
|
const auto taken = std::move(*i);
|
|
_layers.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::updateLayerBoxes() {
|
|
const auto layerBox = [&] {
|
|
if (const auto layer = currentLayer()) {
|
|
return layer->geometry();
|
|
}
|
|
return QRect();
|
|
}();
|
|
const auto specialLayerBox = _specialLayer
|
|
? _specialLayer->geometry()
|
|
: QRect();
|
|
_background->setLayerBoxes(specialLayerBox, layerBox);
|
|
update();
|
|
}
|
|
|
|
void LayerStackWidget::finishAnimating() {
|
|
_background->finishAnimating();
|
|
}
|
|
|
|
bool LayerStackWidget::canSetFocus() const {
|
|
return (currentLayer() || _specialLayer || _mainMenu);
|
|
}
|
|
|
|
void LayerStackWidget::setInnerFocus() {
|
|
if (_background->animating()) {
|
|
setFocus();
|
|
} else if (auto l = currentLayer()) {
|
|
l->setInnerFocus();
|
|
} else if (_specialLayer) {
|
|
_specialLayer->setInnerFocus();
|
|
} else if (_mainMenu) {
|
|
_mainMenu->setInnerFocus();
|
|
}
|
|
}
|
|
|
|
bool LayerStackWidget::contentOverlapped(const QRect &globalRect) {
|
|
if (isHidden()) {
|
|
return false;
|
|
}
|
|
if (_specialLayer && _specialLayer->overlaps(globalRect)) {
|
|
return true;
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
return layer->overlaps(globalRect);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename SetupNew, typename ClearOld>
|
|
bool LayerStackWidget::prepareAnimation(
|
|
SetupNew &&setupNewWidgets,
|
|
ClearOld &&clearOldWidgets,
|
|
Action action,
|
|
anim::type animated) {
|
|
if (animated == anim::type::instant) {
|
|
setupNewWidgets();
|
|
clearOldWidgets();
|
|
prepareForAnimation();
|
|
_background->skipAnimation(action);
|
|
} else {
|
|
setupNewWidgets();
|
|
setCacheImages();
|
|
const auto weak = Ui::MakeWeak(this);
|
|
clearOldWidgets();
|
|
if (weak) {
|
|
prepareForAnimation();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename SetupNew, typename ClearOld>
|
|
void LayerStackWidget::startAnimation(
|
|
SetupNew &&setupNewWidgets,
|
|
ClearOld &&clearOldWidgets,
|
|
Action action,
|
|
anim::type animated) {
|
|
const auto alive = prepareAnimation(
|
|
std::forward<SetupNew>(setupNewWidgets),
|
|
std::forward<ClearOld>(clearOldWidgets),
|
|
action,
|
|
animated);
|
|
if (alive) {
|
|
_background->startAnimation(action);
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::resizeEvent(QResizeEvent *e) {
|
|
const auto weak = Ui::MakeWeak(this);
|
|
_background->setGeometry(rect());
|
|
if (!weak) {
|
|
return;
|
|
}
|
|
if (_specialLayer) {
|
|
_specialLayer->parentResized();
|
|
if (!weak) {
|
|
return;
|
|
}
|
|
}
|
|
if (const auto layer = currentLayer()) {
|
|
layer->parentResized();
|
|
if (!weak) {
|
|
return;
|
|
}
|
|
}
|
|
if (_mainMenu) {
|
|
_mainMenu->parentResized();
|
|
if (!weak) {
|
|
return;
|
|
}
|
|
}
|
|
updateLayerBoxes();
|
|
}
|
|
|
|
void LayerStackWidget::prepareForAnimation() {
|
|
if (isHidden()) {
|
|
show();
|
|
}
|
|
if (_mainMenu) {
|
|
if (Ui::InFocusChain(_mainMenu)) {
|
|
setFocus();
|
|
}
|
|
_mainMenu->hide();
|
|
}
|
|
if (_specialLayer) {
|
|
if (Ui::InFocusChain(_specialLayer)) {
|
|
setFocus();
|
|
}
|
|
_specialLayer->hide();
|
|
}
|
|
if (const auto layer = currentLayer()) {
|
|
if (Ui::InFocusChain(layer)) {
|
|
setFocus();
|
|
}
|
|
layer->hide();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::animationDone() {
|
|
auto &integration = base::Integration::Instance();
|
|
bool hidden = true;
|
|
if (_mainMenu) {
|
|
integration.setCrashAnnotation("ShowingWidget", u"MainMenu"_q);
|
|
_mainMenu->show();
|
|
hidden = false;
|
|
}
|
|
if (_specialLayer) {
|
|
integration.setCrashAnnotation("ShowingWidget", u"SpecialLayer"_q);
|
|
_specialLayer->show();
|
|
hidden = false;
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
integration.setCrashAnnotation("ShowingWidget", u"Box"_q);
|
|
layer->show();
|
|
hidden = false;
|
|
}
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
if (hidden) {
|
|
_hideFinishStream.fire({});
|
|
} else {
|
|
integration.setCrashAnnotation("ShowingWidget", u"Finished"_q);
|
|
showFinished();
|
|
integration.setCrashAnnotation("ShowingWidget", QString());
|
|
}
|
|
}
|
|
|
|
rpl::producer<> LayerStackWidget::hideFinishEvents() const {
|
|
return _hideFinishStream.events();
|
|
}
|
|
|
|
void LayerStackWidget::showFinished() {
|
|
fixOrder();
|
|
sendFakeMouseEvent();
|
|
updateLayerBoxes();
|
|
if (_specialLayer) {
|
|
_specialLayer->showFinished();
|
|
}
|
|
if (_mainMenu) {
|
|
_mainMenu->showFinished();
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
layer->showFinished();
|
|
}
|
|
if (canSetFocus()) {
|
|
setInnerFocus();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::showSpecialLayer(
|
|
object_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
startAnimation([&] {
|
|
_specialLayer.destroy();
|
|
_specialLayer = std::move(layer);
|
|
initChildLayer(_specialLayer);
|
|
}, [&] {
|
|
_mainMenu.destroy();
|
|
}, Action::ShowSpecialLayer, animated);
|
|
}
|
|
|
|
bool LayerStackWidget::showSectionInternal(
|
|
not_null<::Window::SectionMemento*> memento,
|
|
const ::Window::SectionShow ¶ms) {
|
|
if (_specialLayer) {
|
|
return _specialLayer->showSectionInternal(memento, params);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LayerStackWidget::hideSpecialLayer(anim::type animated) {
|
|
startAnimation([] {}, [&] {
|
|
clearSpecialLayer();
|
|
_mainMenu.destroy();
|
|
}, Action::HideSpecialLayer, animated);
|
|
}
|
|
|
|
void LayerStackWidget::showMainMenu(
|
|
object_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
startAnimation([&] {
|
|
_mainMenu = std::move(layer);
|
|
initChildLayer(_mainMenu);
|
|
_mainMenu->moveToLeft(0, 0);
|
|
}, [&] {
|
|
clearLayers();
|
|
_specialLayer.destroy();
|
|
}, Action::ShowMainMenu, animated);
|
|
}
|
|
|
|
void LayerStackWidget::showBox(
|
|
object_ptr<BoxContent> box,
|
|
LayerOptions options,
|
|
anim::type animated) {
|
|
showLayer(
|
|
std::make_unique<BoxLayerWidget>(this, std::move(box)),
|
|
options,
|
|
animated);
|
|
}
|
|
|
|
void LayerStackWidget::showLayer(
|
|
std::unique_ptr<LayerWidget> layer,
|
|
LayerOptions options,
|
|
anim::type animated) {
|
|
if (options & LayerOption::KeepOther) {
|
|
if (options & LayerOption::ShowAfterOther) {
|
|
prependLayer(std::move(layer), animated);
|
|
} else {
|
|
appendLayer(std::move(layer), animated);
|
|
}
|
|
} else {
|
|
replaceLayer(std::move(layer), animated);
|
|
}
|
|
}
|
|
|
|
LayerWidget *LayerStackWidget::pushLayer(
|
|
std::unique_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
const auto oldLayer = currentLayer();
|
|
if (oldLayer) {
|
|
if (Ui::InFocusChain(oldLayer)) {
|
|
setFocus();
|
|
}
|
|
oldLayer->hide();
|
|
}
|
|
_layers.push_back(std::move(layer));
|
|
const auto raw = _layers.back().get();
|
|
initChildLayer(raw);
|
|
|
|
if (_layers.size() > 1) {
|
|
if (!_background->animating()) {
|
|
raw->setVisible(true);
|
|
showFinished();
|
|
}
|
|
} else {
|
|
startAnimation([] {}, [&] {
|
|
_mainMenu.destroy();
|
|
}, Action::ShowLayer, animated);
|
|
}
|
|
|
|
return raw;
|
|
}
|
|
|
|
void LayerStackWidget::appendLayer(
|
|
std::unique_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
pushLayer(std::move(layer), animated);
|
|
}
|
|
|
|
void LayerStackWidget::prependLayer(
|
|
std::unique_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
if (_layers.empty()) {
|
|
replaceLayer(std::move(layer), animated);
|
|
return;
|
|
}
|
|
_layers.insert(
|
|
begin(_layers),
|
|
std::move(layer));
|
|
const auto raw = _layers.front().get();
|
|
raw->hide();
|
|
initChildLayer(raw);
|
|
}
|
|
|
|
void LayerStackWidget::replaceLayer(
|
|
std::unique_ptr<LayerWidget> layer,
|
|
anim::type animated) {
|
|
const auto pointer = pushLayer(std::move(layer), animated);
|
|
const auto removeTill = ranges::find(
|
|
_layers,
|
|
pointer,
|
|
&std::unique_ptr<LayerWidget>::get);
|
|
_closingLayers.insert(
|
|
end(_closingLayers),
|
|
std::make_move_iterator(begin(_layers)),
|
|
std::make_move_iterator(removeTill));
|
|
_layers.erase(begin(_layers), removeTill);
|
|
clearClosingLayers();
|
|
}
|
|
|
|
bool LayerStackWidget::takeToThirdSection() {
|
|
return _specialLayer
|
|
? _specialLayer->takeToThirdSection()
|
|
: false;
|
|
}
|
|
|
|
void LayerStackWidget::clearLayers() {
|
|
_closingLayers.insert(
|
|
end(_closingLayers),
|
|
std::make_move_iterator(begin(_layers)),
|
|
std::make_move_iterator(end(_layers)));
|
|
_layers.clear();
|
|
clearClosingLayers();
|
|
}
|
|
|
|
void LayerStackWidget::clearClosingLayers() {
|
|
const auto weak = Ui::MakeWeak(this);
|
|
while (!_closingLayers.empty()) {
|
|
const auto index = _closingLayers.size() - 1;
|
|
const auto layer = _closingLayers.back().get();
|
|
if (Ui::InFocusChain(layer)) {
|
|
setFocus();
|
|
}
|
|
|
|
// This may destroy LayerStackWidget (by calling Ui::hideLayer).
|
|
// So each time we check a weak pointer (if we are still alive).
|
|
layer->setClosing();
|
|
|
|
// setClosing() could destroy 'this' or could call clearLayers().
|
|
if (weak && !_closingLayers.empty()) {
|
|
// We could enqueue more closing layers, so we remove by index.
|
|
Assert(index < _closingLayers.size());
|
|
Assert(_closingLayers[index].get() == layer);
|
|
_closingLayers.erase(begin(_closingLayers) + index);
|
|
} else {
|
|
// Everything was destroyed in clearLayers or ~LayerStackWidget.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::clearSpecialLayer() {
|
|
if (_specialLayer) {
|
|
_specialLayer->setClosing();
|
|
_specialLayer.destroy();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::initChildLayer(LayerWidget *layer) {
|
|
layer->setParent(this);
|
|
layer->setClosedCallback([=] { closeLayer(layer); });
|
|
layer->setResizedCallback([=] { updateLayerBoxes(); });
|
|
Ui::SendPendingMoveResizeEvents(layer);
|
|
layer->parentResized();
|
|
}
|
|
|
|
void LayerStackWidget::fixOrder() {
|
|
if (const auto layer = currentLayer()) {
|
|
_background->raise();
|
|
layer->raise();
|
|
} else if (_specialLayer) {
|
|
_specialLayer->raise();
|
|
}
|
|
if (_mainMenu) {
|
|
_mainMenu->raise();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::sendFakeMouseEvent() {
|
|
SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
|
|
}
|
|
|
|
LayerStackWidget::~LayerStackWidget() {
|
|
// Some layer destructors call back into LayerStackWidget.
|
|
while (!_layers.empty() || !_closingLayers.empty()) {
|
|
hideAll(anim::type::instant);
|
|
clearClosingLayers();
|
|
}
|
|
}
|
|
|
|
} // namespace Ui
|