mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-22 07:09:25 +00:00
Currently the build without implicitly included precompiled header is not supported anyway (because Qt MOC source files do not include stdafx.h, they include plain headers). So when we decide to support building without implicitly included precompiled headers we'll have to fix all the headers anyway.
978 lines
28 KiB
C++
978 lines
28 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
*/
|
|
#include "lang.h"
|
|
|
|
#include "media/media_clip_reader.h"
|
|
#include "boxes/abstractbox.h"
|
|
#include "layerwidget.h"
|
|
#include "application.h"
|
|
#include "mainwindow.h"
|
|
#include "mainwidget.h"
|
|
#include "core/file_utilities.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_widgets.h"
|
|
#include "styles/style_stickers.h"
|
|
#include "ui/widgets/shadow.h"
|
|
#include "window/window_main_menu.h"
|
|
|
|
namespace {
|
|
|
|
constexpr int kStickerPreviewEmojiLimit = 10;
|
|
|
|
} // namespace
|
|
|
|
class LayerStackWidget::BackgroundWidget : public TWidget {
|
|
public:
|
|
BackgroundWidget(QWidget *parent) : TWidget(parent) {
|
|
}
|
|
|
|
void setDoneCallback(base::lambda<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 startAnimation(Action action);
|
|
void finishAnimation();
|
|
|
|
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;
|
|
|
|
base::lambda<void()> _doneCallback;
|
|
|
|
bool _wasAnimating = false;
|
|
bool _inPaintEvent = false;
|
|
Animation _a_shown;
|
|
Animation _a_mainMenuShown;
|
|
Animation _a_specialLayerShown;
|
|
Animation _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;
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::startAnimation(Action action) {
|
|
if (action == Action::ShowMainMenu) {
|
|
setMainMenuShown(true);
|
|
} else if (action != Action::HideLayer) {
|
|
setMainMenuShown(false);
|
|
}
|
|
if (action == Action::ShowSpecialLayer) {
|
|
setSpecialLayerShown(true);
|
|
} else if (action == Action::ShowMainMenu || action == Action::HideAll) {
|
|
setSpecialLayerShown(false);
|
|
}
|
|
if (action == Action::ShowLayer) {
|
|
setLayerShown(true);
|
|
} else {
|
|
setLayerShown(false);
|
|
}
|
|
_wasAnimating = true;
|
|
checkIfDone();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::checkIfDone() {
|
|
if (!_wasAnimating || _inPaintEvent || animating()) {
|
|
return;
|
|
}
|
|
_wasAnimating = false;
|
|
_bodyCache = _mainMenuCache = _specialLayerCache = _layerCache = QPixmap();
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
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() / cIntRetinaFactor()) - 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 = base::scope_guard([this] {
|
|
_inPaintEvent = false;
|
|
checkIfDone();
|
|
});
|
|
|
|
if (!_bodyCache.isNull()) {
|
|
p.drawPixmap(0, 0, _bodyCache);
|
|
}
|
|
|
|
auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox;
|
|
auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox;
|
|
|
|
auto ms = getms();
|
|
auto mainMenuProgress = _a_mainMenuShown.current(ms, -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.current(ms, isShown() ? 1. : 0.);
|
|
auto specialLayerOpacity = _a_specialLayerShown.current(ms, _specialLayerShown ? 1. : 0.);
|
|
auto layerOpacity = _a_layerShown.current(ms, _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, Ui::Shadow::Side::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 = Ui::Shadow::Side::Left | Ui::Shadow::Side::Right;
|
|
auto topCorners = (specialLayerBox.y() > 0);
|
|
auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height());
|
|
if (topCorners) {
|
|
sides |= Ui::Shadow::Side::Top;
|
|
}
|
|
if (bottomCorners) {
|
|
sides |= Ui::Shadow::Side::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, sides);
|
|
|
|
if (topCorners || bottomCorners) {
|
|
// In case of painting the shadow above the special layer we get
|
|
// glitches in the corners, so we need to paint the corners once more.
|
|
p.setClipping(false);
|
|
auto parts = (topCorners ? (App::RectPart::TopLeft | App::RectPart::TopRight) : App::RectPart::None)
|
|
| (bottomCorners ? (App::RectPart::BottomLeft | App::RectPart::BottomRight) : App::RectPart::None);
|
|
App::roundRect(p, specialLayerBox, st::boxBg, BoxCorners, nullptr, parts);
|
|
}
|
|
}
|
|
|
|
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.
|
|
auto region = QRegion(bg) - specialLayerBox;
|
|
for (auto rect : region.rects()) {
|
|
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 * cIntRetinaFactor();
|
|
auto sourceRect = rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width());
|
|
p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect);
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::finishAnimation() {
|
|
_a_shown.finish();
|
|
_a_mainMenuShown.finish();
|
|
_a_specialLayerShown.finish();
|
|
_a_layerShown.finish();
|
|
checkIfDone();
|
|
}
|
|
|
|
void LayerStackWidget::BackgroundWidget::animationCallback() {
|
|
update();
|
|
checkIfDone();
|
|
}
|
|
|
|
LayerStackWidget::LayerStackWidget(QWidget *parent) : TWidget(parent)
|
|
, _background(this) {
|
|
setGeometry(parentWidget()->rect());
|
|
hide();
|
|
_background->setDoneCallback([this] { animationDone(); });
|
|
}
|
|
|
|
void LayerWidget::setInnerFocus() {
|
|
if (!isAncestorOf(App::wnd()->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 LayerStackWidget::keyPressEvent(QKeyEvent *e) {
|
|
if (e->key() == Qt::Key_Escape) {
|
|
hideCurrent();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::mousePressEvent(QMouseEvent *e) {
|
|
hideCurrent();
|
|
}
|
|
|
|
void LayerStackWidget::hideCurrent() {
|
|
return currentLayer() ? hideLayers() : hideAll();
|
|
}
|
|
|
|
void LayerStackWidget::hideLayers() {
|
|
startAnimation([] {}, [this] {
|
|
clearLayers();
|
|
}, Action::HideLayer);
|
|
}
|
|
|
|
void LayerStackWidget::hideAll() {
|
|
startAnimation([] {}, [this] {
|
|
clearLayers();
|
|
clearSpecialLayer();
|
|
_mainMenu.destroyDelayed();
|
|
}, Action::HideAll);
|
|
}
|
|
|
|
void LayerStackWidget::hideTopLayer() {
|
|
if (_specialLayer) {
|
|
hideLayers();
|
|
} else {
|
|
hideAll();
|
|
}
|
|
}
|
|
|
|
bool LayerStackWidget::layerShown() const {
|
|
return _specialLayer || currentLayer();
|
|
}
|
|
|
|
void LayerStackWidget::setCacheImages() {
|
|
auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
|
|
auto specialLayerCache = QPixmap();
|
|
if (_specialLayer) {
|
|
auto sides = Ui::Shadow::Side::Left | Ui::Shadow::Side::Right;
|
|
if (_specialLayer->y() > 0) {
|
|
sides |= Ui::Shadow::Side::Top;
|
|
}
|
|
if (_specialLayer->y() + _specialLayer->height() < height()) {
|
|
sides |= Ui::Shadow::Side::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(App::wnd()->focusWidget())) {
|
|
setFocus();
|
|
}
|
|
if (_mainMenu) {
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
hideChildren();
|
|
bodyCache = myGrab(App::wnd()->bodyWidget());
|
|
showChildren();
|
|
mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, Ui::Shadow::Side::Right);
|
|
}
|
|
setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull());
|
|
updateLayerBoxes();
|
|
_background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache));
|
|
}
|
|
|
|
void LayerStackWidget::onLayerClosed(LayerWidget *layer) {
|
|
if (!layer->setClosing()) {
|
|
// This layer is already closing.
|
|
return;
|
|
}
|
|
layer->deleteLater();
|
|
if (layer == _specialLayer) {
|
|
hideAll();
|
|
} else if (layer == currentLayer()) {
|
|
if (_layers.size() == 1) {
|
|
hideCurrent();
|
|
} else {
|
|
if (layer->inFocusChain()) setFocus();
|
|
layer->hide();
|
|
_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) {
|
|
_layers.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::onLayerResized() {
|
|
updateLayerBoxes();
|
|
}
|
|
|
|
void LayerStackWidget::updateLayerBoxes() {
|
|
auto getLayerBox = [this]() {
|
|
if (auto layer = currentLayer()) {
|
|
return layer->geometry();
|
|
}
|
|
return QRect();
|
|
};
|
|
auto getSpecialLayerBox = [this]() {
|
|
return _specialLayer ? _specialLayer->geometry() : QRect();
|
|
};
|
|
_background->setLayerBoxes(getSpecialLayerBox(), getLayerBox());
|
|
update();
|
|
}
|
|
|
|
void LayerStackWidget::finishAnimation() {
|
|
_background->finishAnimation();
|
|
}
|
|
|
|
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>
|
|
void LayerStackWidget::startAnimation(SetupNew setupNewWidgets, ClearOld clearOldWidgets, Action action) {
|
|
if (App::quitting()) return;
|
|
|
|
setupNewWidgets();
|
|
setCacheImages();
|
|
clearOldWidgets();
|
|
prepareForAnimation();
|
|
_background->startAnimation(action);
|
|
}
|
|
|
|
void LayerStackWidget::resizeEvent(QResizeEvent *e) {
|
|
_background->setGeometry(rect());
|
|
if (_specialLayer) {
|
|
_specialLayer->parentResized();
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
layer->parentResized();
|
|
}
|
|
if (_mainMenu) {
|
|
_mainMenu->resize(_mainMenu->width(), height());
|
|
}
|
|
updateLayerBoxes();
|
|
}
|
|
|
|
void LayerStackWidget::showBox(object_ptr<BoxContent> box) {
|
|
auto pointer = pushBox(std::move(box));
|
|
while (!_layers.isEmpty() && _layers.front() != pointer) {
|
|
auto removingLayer = _layers.front();
|
|
_layers.pop_front();
|
|
|
|
removingLayer->setClosing();
|
|
if (removingLayer->inFocusChain()) setFocus();
|
|
removingLayer->hide();
|
|
removingLayer->deleteLater();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::prepareForAnimation() {
|
|
if (isHidden()) {
|
|
show();
|
|
}
|
|
if (_mainMenu) {
|
|
_mainMenu->hide();
|
|
}
|
|
if (_specialLayer) {
|
|
_specialLayer->hide();
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
layer->hide();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::animationDone() {
|
|
bool hidden = true;
|
|
if (_mainMenu) {
|
|
_mainMenu->show();
|
|
hidden = false;
|
|
}
|
|
if (_specialLayer) {
|
|
_specialLayer->show();
|
|
hidden = false;
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
layer->show();
|
|
hidden = false;
|
|
}
|
|
if (hidden) {
|
|
App::wnd()->layerFinishedHide(this);
|
|
} else {
|
|
showFinished();
|
|
}
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
}
|
|
|
|
void LayerStackWidget::showFinished() {
|
|
fixOrder();
|
|
sendFakeMouseEvent();
|
|
updateLayerBoxes();
|
|
if (_mainMenu) {
|
|
_mainMenu->showFinished();
|
|
}
|
|
if (_specialLayer) {
|
|
_specialLayer->showFinished();
|
|
}
|
|
if (auto layer = currentLayer()) {
|
|
layer->showFinished();
|
|
}
|
|
if (auto window = App::wnd()) {
|
|
window->setInnerFocus();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::showSpecialLayer(object_ptr<LayerWidget> layer) {
|
|
startAnimation([this, layer = std::move(layer)]() mutable {
|
|
_specialLayer.destroyDelayed();
|
|
_specialLayer = std::move(layer);
|
|
initChildLayer(_specialLayer);
|
|
}, [this] {
|
|
clearLayers();
|
|
_mainMenu.destroyDelayed();
|
|
}, Action::ShowSpecialLayer);
|
|
}
|
|
|
|
void LayerStackWidget::showMainMenu() {
|
|
startAnimation([this] {
|
|
_mainMenu.create(this);
|
|
_mainMenu->setGeometryToLeft(0, 0, _mainMenu->width(), height());
|
|
_mainMenu->setParent(this);
|
|
}, [this] {
|
|
clearLayers();
|
|
_specialLayer.destroyDelayed();
|
|
}, Action::ShowMainMenu);
|
|
}
|
|
|
|
void LayerStackWidget::appendBox(object_ptr<BoxContent> box) {
|
|
pushBox(std::move(box));
|
|
}
|
|
|
|
LayerWidget *LayerStackWidget::pushBox(object_ptr<BoxContent> box) {
|
|
auto oldLayer = currentLayer();
|
|
if (oldLayer) {
|
|
if (oldLayer->inFocusChain()) setFocus();
|
|
oldLayer->hide();
|
|
}
|
|
auto layer = object_ptr<AbstractBox>(this, std::move(box));
|
|
_layers.push_back(layer);
|
|
initChildLayer(layer);
|
|
|
|
if (_layers.size() > 1) {
|
|
if (!_background->animating()) {
|
|
layer->show();
|
|
showFinished();
|
|
}
|
|
} else {
|
|
startAnimation([] {}, [this] {
|
|
_mainMenu.destroyDelayed();
|
|
}, Action::ShowLayer);
|
|
}
|
|
|
|
return layer.data();
|
|
}
|
|
|
|
void LayerStackWidget::prependBox(object_ptr<BoxContent> box) {
|
|
if (_layers.empty()) {
|
|
return showBox(std::move(box));
|
|
}
|
|
auto layer = object_ptr<AbstractBox>(this, std::move(box));
|
|
layer->hide();
|
|
_layers.push_front(layer);
|
|
initChildLayer(layer);
|
|
}
|
|
|
|
void LayerStackWidget::clearLayers() {
|
|
for (auto layer : base::take(_layers)) {
|
|
layer->setClosing();
|
|
if (layer->inFocusChain()) setFocus();
|
|
layer->hide();
|
|
layer->deleteLater();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::clearSpecialLayer() {
|
|
if (_specialLayer) {
|
|
_specialLayer->setClosing();
|
|
_specialLayer.destroyDelayed();
|
|
}
|
|
}
|
|
|
|
void LayerStackWidget::initChildLayer(LayerWidget *layer) {
|
|
layer->setParent(this);
|
|
layer->setClosedCallback([this, layer] { onLayerClosed(layer); });
|
|
layer->setResizedCallback([this] { onLayerResized(); });
|
|
connect(layer, SIGNAL(destroyed(QObject*)), this, SLOT(onLayerDestroyed(QObject*)));
|
|
layer->parentResized();
|
|
}
|
|
|
|
void LayerStackWidget::fixOrder() {
|
|
if (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);
|
|
}
|
|
|
|
void LayerStackWidget::onLayerDestroyed(QObject *obj) {
|
|
if (obj == _specialLayer) {
|
|
_specialLayer = nullptr;
|
|
hideAll();
|
|
} else if (obj == currentLayer()) {
|
|
_layers.pop_back();
|
|
if (auto newLayer = currentLayer()) {
|
|
newLayer->parentResized();
|
|
if (!_background->animating()) {
|
|
newLayer->show();
|
|
showFinished();
|
|
}
|
|
} else if (!_specialLayer) {
|
|
hideAll();
|
|
}
|
|
} else {
|
|
for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) {
|
|
if (obj == *i) {
|
|
_layers.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LayerStackWidget::~LayerStackWidget() {
|
|
if (App::wnd()) App::wnd()->noLayerStack(this);
|
|
}
|
|
|
|
MediaPreviewWidget::MediaPreviewWidget(QWidget *parent) : TWidget(parent)
|
|
, _emojiSize(Ui::Emoji::Size(Ui::Emoji::Index() + 1) / cIntRetinaFactor()) {
|
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
subscribe(FileDownload::ImageLoaded(), [this] { update(); });
|
|
}
|
|
|
|
void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
QRect r(e->rect());
|
|
|
|
auto image = currentImage();
|
|
int w = image.width() / cIntRetinaFactor(), h = image.height() / cIntRetinaFactor();
|
|
auto shown = _a_shown.current(getms(), _hiding ? 0. : 1.);
|
|
if (!_a_shown.animating()) {
|
|
if (_hiding) {
|
|
hide();
|
|
return;
|
|
}
|
|
} else {
|
|
p.setOpacity(shown);
|
|
// w = qMax(qRound(w * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(w % 2), 1);
|
|
// h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1);
|
|
}
|
|
p.fillRect(r, st::stickerPreviewBg);
|
|
p.drawPixmap((width() - w) / 2, (height() - h) / 2, image);
|
|
if (!_emojiList.isEmpty()) {
|
|
auto emojiCount = _emojiList.size();
|
|
auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
|
|
auto emojiLeft = (width() - emojiWidth) / 2;
|
|
auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1);
|
|
for_const (auto emoji, _emojiList) {
|
|
p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - (_emojiSize * 2), width(), App::emojiLarge(), QRect(emoji->x() * esize, emoji->y() * esize, esize, esize));
|
|
emojiLeft += _emojiSize + st::stickerEmojiSkip;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MediaPreviewWidget::resizeEvent(QResizeEvent *e) {
|
|
update();
|
|
}
|
|
|
|
void MediaPreviewWidget::showPreview(DocumentData *document) {
|
|
if (!document || (!document->isAnimation() && !document->sticker())) {
|
|
hidePreview();
|
|
return;
|
|
}
|
|
|
|
startShow();
|
|
_photo = nullptr;
|
|
_document = document;
|
|
fillEmojiString();
|
|
resetGifAndCache();
|
|
}
|
|
|
|
void MediaPreviewWidget::showPreview(PhotoData *photo) {
|
|
if (!photo || photo->full->isNull()) {
|
|
hidePreview();
|
|
return;
|
|
}
|
|
|
|
startShow();
|
|
_photo = photo;
|
|
_document = nullptr;
|
|
fillEmojiString();
|
|
resetGifAndCache();
|
|
}
|
|
|
|
void MediaPreviewWidget::startShow() {
|
|
_cache = QPixmap();
|
|
if (isHidden() || _a_shown.animating()) {
|
|
if (isHidden()) show();
|
|
_hiding = false;
|
|
_a_shown.start([this] { update(); }, 0., 1., st::stickerPreviewDuration);
|
|
} else {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void MediaPreviewWidget::hidePreview() {
|
|
if (isHidden()) {
|
|
return;
|
|
}
|
|
if (_gif) _cache = currentImage();
|
|
_hiding = true;
|
|
_a_shown.start([this] { update(); }, 1., 0., st::stickerPreviewDuration);
|
|
_photo = nullptr;
|
|
_document = nullptr;
|
|
resetGifAndCache();
|
|
}
|
|
|
|
void MediaPreviewWidget::fillEmojiString() {
|
|
auto getStickerEmojiList = [this](uint64 setId) {
|
|
QList<EmojiPtr> result;
|
|
auto &sets = Global::StickerSets();
|
|
auto it = sets.constFind(setId);
|
|
if (it == sets.cend()) {
|
|
return result;
|
|
}
|
|
for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) {
|
|
for_const (auto document, *i) {
|
|
if (document == _document) {
|
|
result.append(i.key());
|
|
if (result.size() >= kStickerPreviewEmojiLimit) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
if (_photo) {
|
|
_emojiList.clear();
|
|
} else if (auto sticker = _document->sticker()) {
|
|
auto &inputSet = sticker->set;
|
|
if (inputSet.type() == mtpc_inputStickerSetID) {
|
|
_emojiList = getStickerEmojiList(inputSet.c_inputStickerSetID().vid.v);
|
|
} else {
|
|
_emojiList.clear();
|
|
if (auto emoji = Ui::Emoji::Find(sticker->alt)) {
|
|
_emojiList.append(emoji);
|
|
}
|
|
}
|
|
} else {
|
|
_emojiList.clear();
|
|
}
|
|
}
|
|
|
|
void MediaPreviewWidget::resetGifAndCache() {
|
|
_gif.reset();
|
|
_cacheStatus = CacheNotLoaded;
|
|
_cachedSize = QSize();
|
|
}
|
|
|
|
QSize MediaPreviewWidget::currentDimensions() const {
|
|
if (!_cachedSize.isEmpty()) {
|
|
return _cachedSize;
|
|
}
|
|
if (!_document && !_photo) {
|
|
_cachedSize = QSize(_cache.width() / cIntRetinaFactor(), _cache.height() / cIntRetinaFactor());
|
|
return _cachedSize;
|
|
}
|
|
|
|
QSize result, box;
|
|
if (_photo) {
|
|
result = QSize(_photo->full->width(), _photo->full->height());
|
|
box = QSize(width() - 2 * st::boxVerticalMargin, height() - 2 * st::boxVerticalMargin);
|
|
} else {
|
|
result = _document->dimensions;
|
|
if (_gif && _gif->ready()) {
|
|
result = QSize(_gif->width(), _gif->height());
|
|
}
|
|
if (_document->sticker()) {
|
|
box = QSize(st::maxStickerSize, st::maxStickerSize);
|
|
} else {
|
|
box = QSize(2 * st::maxStickerSize, 2 * st::maxStickerSize);
|
|
}
|
|
}
|
|
result = QSize(qMax(convertScale(result.width()), 1), qMax(convertScale(result.height()), 1));
|
|
if (result.width() > box.width()) {
|
|
result.setHeight(qMax((box.width() * result.height()) / result.width(), 1));
|
|
result.setWidth(box.width());
|
|
}
|
|
if (result.height() > box.height()) {
|
|
result.setWidth(qMax((box.height() * result.width()) / result.height(), 1));
|
|
result.setHeight(box.height());
|
|
}
|
|
if (_photo) {
|
|
_cachedSize = result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QPixmap MediaPreviewWidget::currentImage() const {
|
|
if (_document) {
|
|
if (_document->sticker()) {
|
|
if (_cacheStatus != CacheLoaded) {
|
|
_document->checkSticker();
|
|
if (_document->sticker()->img->isNull()) {
|
|
if (_cacheStatus != CacheThumbLoaded && _document->thumb->loaded()) {
|
|
QSize s = currentDimensions();
|
|
_cache = _document->thumb->pixBlurred(s.width(), s.height());
|
|
_cacheStatus = CacheThumbLoaded;
|
|
}
|
|
} else {
|
|
QSize s = currentDimensions();
|
|
_cache = _document->sticker()->img->pix(s.width(), s.height());
|
|
_cacheStatus = CacheLoaded;
|
|
}
|
|
}
|
|
} else {
|
|
_document->automaticLoad(nullptr);
|
|
if (_document->loaded()) {
|
|
if (!_gif && !_gif.isBad()) {
|
|
auto that = const_cast<MediaPreviewWidget*>(this);
|
|
that->_gif = Media::Clip::MakeReader(_document->location(), _document->data(), [this, that](Media::Clip::Notification notification) {
|
|
that->clipCallback(notification);
|
|
});
|
|
if (_gif) _gif->setAutoplay();
|
|
}
|
|
}
|
|
if (_gif && _gif->started()) {
|
|
QSize s = currentDimensions();
|
|
return _gif->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, ImageRoundCorner::None, getms());
|
|
}
|
|
if (_cacheStatus != CacheThumbLoaded && _document->thumb->loaded()) {
|
|
QSize s = currentDimensions();
|
|
_cache = _document->thumb->pixBlurred(s.width(), s.height());
|
|
_cacheStatus = CacheThumbLoaded;
|
|
}
|
|
}
|
|
} else if (_photo) {
|
|
if (_cacheStatus != CacheLoaded) {
|
|
if (_photo->full->loaded()) {
|
|
QSize s = currentDimensions();
|
|
_cache = _photo->full->pix(s.width(), s.height());
|
|
_cacheStatus = CacheLoaded;
|
|
} else {
|
|
if (_cacheStatus != CacheThumbLoaded && _photo->thumb->loaded()) {
|
|
QSize s = currentDimensions();
|
|
_cache = _photo->thumb->pixBlurred(s.width(), s.height());
|
|
_cacheStatus = CacheThumbLoaded;
|
|
}
|
|
_photo->thumb->load();
|
|
_photo->full->load();
|
|
}
|
|
}
|
|
|
|
}
|
|
return _cache;
|
|
}
|
|
|
|
void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
|
|
using namespace Media::Clip;
|
|
switch (notification) {
|
|
case NotificationReinit: {
|
|
if (_gif && _gif->state() == State::Error) {
|
|
_gif.setBad();
|
|
}
|
|
|
|
if (_gif && _gif->ready() && !_gif->started()) {
|
|
QSize s = currentDimensions();
|
|
_gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, ImageRoundCorner::None);
|
|
}
|
|
|
|
update();
|
|
} break;
|
|
|
|
case NotificationRepaint: {
|
|
if (_gif && !_gif->currentDisplayed()) {
|
|
update();
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
MediaPreviewWidget::~MediaPreviewWidget() {
|
|
}
|