mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 18:21:42 -05:00

Also don't change mask corner images when color theme is changed. This prevents race condition in mask corner images access, because the GIF frame readers access mask corner images from other threads.
420 lines
11 KiB
C++
420 lines
11 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 "chat_helpers/tabbed_panel.h"
|
|
|
|
#include "ui/widgets/shadow.h"
|
|
#include "styles/style_chat_helpers.h"
|
|
#include "chat_helpers/tabbed_selector.h"
|
|
#include "window/window_controller.h"
|
|
#include "mainwindow.h"
|
|
|
|
namespace ChatHelpers {
|
|
namespace {
|
|
|
|
constexpr auto kHideTimeoutMs = 300;
|
|
constexpr auto kDelayedHideTimeoutMs = 3000;
|
|
|
|
} // namespace
|
|
|
|
TabbedPanel::TabbedPanel(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TabbedPanel(parent, controller, object_ptr<TabbedSelector>(nullptr, controller)) {
|
|
}
|
|
|
|
TabbedPanel::TabbedPanel(QWidget *parent, gsl::not_null<Window::Controller*> controller, object_ptr<TabbedSelector> selector) : TWidget(parent)
|
|
, _controller(controller)
|
|
, _selector(std::move(selector)) {
|
|
_selector->setParent(this);
|
|
_selector->setRoundRadius(st::buttonRadius);
|
|
_selector->setAfterShownCallback([this](SelectorTab tab) {
|
|
if (tab == SelectorTab::Gifs) {
|
|
_controller->enableGifPauseReason(Window::GifPauseReason::SavedGifs);
|
|
}
|
|
});
|
|
_selector->setBeforeHidingCallback([this](SelectorTab tab) {
|
|
if (tab == SelectorTab::Gifs) {
|
|
_controller->disableGifPauseReason(Window::GifPauseReason::SavedGifs);
|
|
}
|
|
});
|
|
|
|
resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size());
|
|
|
|
_contentMaxHeight = st::emojiPanMaxHeight;
|
|
_contentHeight = _contentMaxHeight;
|
|
|
|
_selector->resize(st::emojiPanWidth, _contentHeight);
|
|
_selector->move(innerRect().topLeft());
|
|
|
|
_hideTimer.setCallback([this] { hideByTimerOrLeave(); });
|
|
|
|
connect(_selector, &TabbedSelector::checkForHide, this, [this] {
|
|
if (!rect().contains(mapFromGlobal(QCursor::pos()))) {
|
|
_hideTimer.callOnce(kDelayedHideTimeoutMs);
|
|
}
|
|
});
|
|
connect(_selector, &TabbedSelector::cancelled, this, [this] {
|
|
hideAnimated();
|
|
});
|
|
connect(_selector, &TabbedSelector::slideFinished, this, [this] {
|
|
InvokeQueued(this, [this] {
|
|
if (_hideAfterSlide) {
|
|
startOpacityAnimation(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
|
|
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
|
|
}
|
|
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
|
|
hideChildren();
|
|
}
|
|
|
|
void TabbedPanel::moveBottom(int bottom) {
|
|
_bottom = bottom;
|
|
updateContentHeight();
|
|
}
|
|
|
|
void TabbedPanel::updateContentHeight() {
|
|
if (isDestroying()) {
|
|
return;
|
|
}
|
|
|
|
auto addedHeight = innerPadding().top() + innerPadding().bottom();
|
|
auto marginsHeight = _selector->marginTop() + _selector->marginBottom();
|
|
auto availableHeight = _bottom - marginsHeight;
|
|
auto wantedContentHeight = qRound(st::emojiPanHeightRatio * availableHeight) - addedHeight;
|
|
auto contentHeight = marginsHeight + snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight);
|
|
auto resultTop = _bottom - addedHeight - contentHeight;
|
|
if (contentHeight == _contentHeight) {
|
|
move(x(), resultTop);
|
|
return;
|
|
}
|
|
|
|
auto was = _contentHeight;
|
|
_contentHeight = contentHeight;
|
|
|
|
resize(QRect(0, 0, innerRect().width(), _contentHeight).marginsAdded(innerPadding()).size());
|
|
move(x(), resultTop);
|
|
|
|
_selector->resize(innerRect().width(), _contentHeight);
|
|
|
|
update();
|
|
}
|
|
|
|
void TabbedPanel::onWndActiveChanged() {
|
|
if (!App::wnd()->windowHandle()->isActive() && !isHidden() && !preventAutoHide()) {
|
|
hideAnimated();
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
auto ms = getms();
|
|
|
|
// This call can finish _a_show animation and destroy _showAnimation.
|
|
auto opacityAnimating = _a_opacity.animating(ms);
|
|
|
|
auto showAnimating = _a_show.animating(ms);
|
|
if (_showAnimation && !showAnimating) {
|
|
_showAnimation.reset();
|
|
if (!opacityAnimating && !isDestroying()) {
|
|
showChildren();
|
|
_selector->afterShown();
|
|
}
|
|
}
|
|
|
|
if (showAnimating) {
|
|
t_assert(_showAnimation != nullptr);
|
|
if (auto opacity = _a_opacity.current(_hiding ? 0. : 1.)) {
|
|
_showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity);
|
|
}
|
|
} else if (opacityAnimating) {
|
|
p.setOpacity(_a_opacity.current(_hiding ? 0. : 1.));
|
|
p.drawPixmap(0, 0, _cache);
|
|
} else if (_hiding || isHidden()) {
|
|
hideFinished();
|
|
} else {
|
|
if (!_cache.isNull()) _cache = QPixmap();
|
|
Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow);
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::moveByBottom() {
|
|
moveToRight(0, y());
|
|
updateContentHeight();
|
|
}
|
|
|
|
void TabbedPanel::enterEventHook(QEvent *e) {
|
|
showAnimated();
|
|
}
|
|
|
|
bool TabbedPanel::preventAutoHide() const {
|
|
if (isDestroying()) {
|
|
return false;
|
|
}
|
|
return _selector->preventAutoHide();
|
|
}
|
|
|
|
void TabbedPanel::leaveEventHook(QEvent *e) {
|
|
if (preventAutoHide()) {
|
|
return;
|
|
}
|
|
auto ms = getms();
|
|
if (_a_show.animating(ms) || _a_opacity.animating(ms)) {
|
|
hideAnimated();
|
|
} else {
|
|
_hideTimer.callOnce(kHideTimeoutMs);
|
|
}
|
|
return TWidget::leaveEventHook(e);
|
|
}
|
|
|
|
void TabbedPanel::otherEnter() {
|
|
showAnimated();
|
|
}
|
|
|
|
void TabbedPanel::otherLeave() {
|
|
if (preventAutoHide()) {
|
|
return;
|
|
}
|
|
|
|
auto ms = getms();
|
|
if (_a_opacity.animating(ms)) {
|
|
hideByTimerOrLeave();
|
|
} else {
|
|
_hideTimer.callOnce(0);
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::hideFast() {
|
|
if (isHidden()) return;
|
|
|
|
_hideTimer.cancel();
|
|
_hiding = false;
|
|
_a_opacity.finish();
|
|
hideFinished();
|
|
}
|
|
|
|
void TabbedPanel::opacityAnimationCallback() {
|
|
update();
|
|
if (!_a_opacity.animating()) {
|
|
if (_hiding || isDestroying()) {
|
|
_hiding = false;
|
|
hideFinished();
|
|
} else if (!_a_show.animating()) {
|
|
showChildren();
|
|
_selector->afterShown();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::hideByTimerOrLeave() {
|
|
if (isHidden() || preventAutoHide()) return;
|
|
|
|
hideAnimated();
|
|
}
|
|
|
|
void TabbedPanel::prepareCache() {
|
|
if (_a_opacity.animating()) return;
|
|
|
|
auto showAnimation = base::take(_a_show);
|
|
auto showAnimationData = base::take(_showAnimation);
|
|
showChildren();
|
|
_cache = myGrab(this);
|
|
_showAnimation = base::take(showAnimationData);
|
|
_a_show = base::take(showAnimation);
|
|
if (_a_show.animating()) {
|
|
hideChildren();
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::startOpacityAnimation(bool hiding) {
|
|
if (_selector && !_selector->isHidden()) {
|
|
_selector->beforeHiding();
|
|
}
|
|
_hiding = false;
|
|
prepareCache();
|
|
_hiding = hiding;
|
|
hideChildren();
|
|
_a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::emojiPanDuration);
|
|
}
|
|
|
|
void TabbedPanel::startShowAnimation() {
|
|
if (!_a_show.animating()) {
|
|
auto image = grabForAnimation();
|
|
|
|
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight);
|
|
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
|
_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
|
|
auto corners = App::cornersMask(ImageRoundRadius::Small);
|
|
_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
|
|
_showAnimation->start();
|
|
}
|
|
hideChildren();
|
|
_a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration);
|
|
}
|
|
|
|
QImage TabbedPanel::grabForAnimation() {
|
|
auto cache = base::take(_cache);
|
|
auto opacityAnimation = base::take(_a_opacity);
|
|
auto showAnimationData = base::take(_showAnimation);
|
|
auto showAnimation = base::take(_a_show);
|
|
|
|
showChildren();
|
|
myEnsureResized(this);
|
|
|
|
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
|
result.fill(Qt::transparent);
|
|
if (_selector) {
|
|
_selector->render(&result, _selector->geometry().topLeft());
|
|
}
|
|
|
|
_a_show = base::take(showAnimation);
|
|
_showAnimation = base::take(showAnimationData);
|
|
_a_opacity = base::take(opacityAnimation);
|
|
_cache = base::take(_cache);
|
|
|
|
return result;
|
|
}
|
|
|
|
void TabbedPanel::hideAnimated() {
|
|
if (isHidden() || _hiding) {
|
|
return;
|
|
}
|
|
|
|
_hideTimer.cancel();
|
|
if (!isDestroying() && _selector->isSliding()) {
|
|
_hideAfterSlide = true;
|
|
} else {
|
|
startOpacityAnimation(true);
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::toggleAnimated() {
|
|
if (isDestroying()) {
|
|
return;
|
|
}
|
|
if (isHidden() || _hiding || _hideAfterSlide) {
|
|
showAnimated();
|
|
} else {
|
|
hideAnimated();
|
|
}
|
|
}
|
|
|
|
object_ptr<TabbedSelector> TabbedPanel::takeSelector() {
|
|
if (!isHidden() && !_hiding) {
|
|
startOpacityAnimation(true);
|
|
}
|
|
return std::move(_selector);
|
|
}
|
|
|
|
QPointer<TabbedSelector> TabbedPanel::getSelector() const {
|
|
return _selector.data();
|
|
}
|
|
|
|
void TabbedPanel::hideFinished() {
|
|
hide();
|
|
_a_show.finish();
|
|
_showAnimation.reset();
|
|
_cache = QPixmap();
|
|
_hiding = false;
|
|
if (isDestroying()) {
|
|
deleteLater();
|
|
} else {
|
|
_selector->hideFinished();
|
|
}
|
|
}
|
|
|
|
void TabbedPanel::showAnimated() {
|
|
_hideTimer.cancel();
|
|
_hideAfterSlide = false;
|
|
showStarted();
|
|
}
|
|
|
|
void TabbedPanel::showStarted() {
|
|
if (isDestroying()) {
|
|
return;
|
|
}
|
|
if (isHidden()) {
|
|
_selector->showStarted();
|
|
moveByBottom();
|
|
show();
|
|
startShowAnimation();
|
|
} else if (_hiding) {
|
|
startOpacityAnimation(false);
|
|
}
|
|
}
|
|
|
|
bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) {
|
|
if (isDestroying()) {
|
|
return false;
|
|
}
|
|
if (e->type() == QEvent::Enter) {
|
|
otherEnter();
|
|
} else if (e->type() == QEvent::Leave) {
|
|
otherLeave();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TabbedPanel::stickersInstalled(uint64 setId) {
|
|
if (isDestroying()) {
|
|
return;
|
|
}
|
|
_selector->stickersInstalled(setId);
|
|
if (isHidden()) {
|
|
moveByBottom();
|
|
startShowAnimation();
|
|
show();
|
|
}
|
|
showChildren();
|
|
showAnimated();
|
|
}
|
|
|
|
style::margins TabbedPanel::innerPadding() const {
|
|
return st::emojiPanMargins;
|
|
}
|
|
|
|
QRect TabbedPanel::innerRect() const {
|
|
return rect().marginsRemoved(innerPadding());
|
|
}
|
|
|
|
QRect TabbedPanel::horizontalRect() const {
|
|
return innerRect().marginsRemoved(style::margins(0, st::buttonRadius, 0, st::buttonRadius));
|
|
}
|
|
|
|
QRect TabbedPanel::verticalRect() const {
|
|
return innerRect().marginsRemoved(style::margins(st::buttonRadius, 0, st::buttonRadius, 0));
|
|
}
|
|
|
|
bool TabbedPanel::overlaps(const QRect &globalRect) const {
|
|
if (isHidden() || !_cache.isNull()) return false;
|
|
|
|
auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size());
|
|
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
|
return inner.marginsRemoved(QMargins(st::buttonRadius, 0, st::buttonRadius, 0)).contains(testRect)
|
|
|| inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect);
|
|
}
|
|
|
|
TabbedPanel::~TabbedPanel() = default;
|
|
|
|
} // namespace ChatHelpers
|