diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b634e6c04..7cfe753c5 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4929,6 +4929,9 @@ void HistoryWidget::startItemRevealAnimations() { 1., HistoryView::ListWidget::kItemRevealDuration, anim::easeOutCirc); + if (item->out()) { + controller()->rotateComplexGradientBackground(); + } } } } diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 49f4bbd60..bad64a8c8 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -875,6 +875,12 @@ QImage ChatBackground::gradientForFill() const { return _gradient; } +void ChatBackground::recacheGradientForFill(QImage gradient) { + if (_gradient.size() == gradient.size()) { + _gradient = std::move(gradient); + } +} + QImage ChatBackground::createCurrentImage() const { if (const auto fill = colorForFill()) { auto result = QImage( diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 95448f494..23b2d140a 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -178,6 +178,7 @@ public: } [[nodiscard]] std::optional colorForFill() const; [[nodiscard]] QImage gradientForFill() const; + void recacheGradientForFill(QImage gradient); [[nodiscard]] QImage createCurrentImage() const; [[nodiscard]] bool tile() const; [[nodiscard]] bool tileDay() const; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 89f31d847..950ebda70 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -74,6 +74,83 @@ constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000); constexpr auto kCacheBackgroundFastTimeout = crl::time(200); constexpr auto kBackgroundFadeDuration = crl::time(200); +[[nodiscard]] CacheBackgroundResult CacheBackground( + const CacheBackgroundRequest &request) { + const auto gradient = request.gradient.isNull() + ? QImage() + : request.recreateGradient + ? Images::GenerateGradient( + request.gradient.size(), + request.gradientColors, + request.gradientRotation) + : request.gradient; + if (request.tile || request.prepared.isNull()) { + auto result = gradient.isNull() + ? QImage( + request.area * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied) + : gradient.scaled( + request.area * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(cRetinaFactor()); + if (!request.prepared.isNull()) { + QPainter p(&result); + if (!gradient.isNull()) { + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(request.patternOpacity); + } + const auto &tiled = request.preparedForTiled; + const auto w = tiled.width() / cRetinaFactor(); + const auto h = tiled.height() / cRetinaFactor(); + auto sx = 0; + auto sy = 0; + const auto cx = qCeil(request.area.width() / w); + const auto cy = qCeil(request.area.height() / h); + for (int i = sx; i < cx; ++i) { + for (int j = sy; j < cy; ++j) { + p.drawImage(QPointF(i * w, j * h), tiled); + } + } + } + return { + .image = std::move(result), + .gradient = gradient, + .area = request.area, + }; + } else { + const auto rects = Window::Theme::ComputeBackgroundRects( + request.area, + request.prepared.size()); + auto image = request.prepared.copy(rects.from).scaled( + rects.to.width() * cIntRetinaFactor(), + rects.to.height() * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + auto result = gradient.isNull() + ? std::move(image) + : gradient.scaled( + image.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(cRetinaFactor()); + if (!gradient.isNull()) { + QPainter p(&result); + p.setCompositionMode(QPainter::CompositionMode_SoftLight); + p.setOpacity(request.patternOpacity); + p.drawImage(QRect(QPoint(), rects.to.size()), image); + } + image = QImage(); + return { + .image = std::move(result), + .gradient = gradient, + .area = request.area, + .x = rects.to.x(), + .y = rects.to.y(), + }; + } +} + } // namespace void ActivateWindow(not_null controller) { @@ -460,6 +537,32 @@ void SessionNavigation::showPollResults( showSection(std::make_shared(poll, contextId), params); } +bool operator==( + const CacheBackgroundRequest &a, + const CacheBackgroundRequest &b) { + return (a.prepared.cacheKey() == b.prepared.cacheKey()) + && (a.area == b.area) + && (a.gradientRotation == b.gradientRotation) + && (a.tile == b.tile) + && (a.recreateGradient == b.recreateGradient) + && (a.gradient.cacheKey() == b.gradient.cacheKey()) + && (a.gradientProgress == b.gradientProgress) + && (a.patternOpacity == b.patternOpacity); +} + +bool operator!=( + const CacheBackgroundRequest &a, + const CacheBackgroundRequest &b) { + return !(a == b); +} + +CachedBackground::CachedBackground(CacheBackgroundResult &&result) +: pixmap(Ui::PixmapFromImage(std::move(result.image))) +, area(result.area) +, x(result.x) +, y(result.y) { +} + SessionController::SessionController( not_null session, not_null window) @@ -1328,95 +1431,123 @@ const BackgroundState &SessionController::backgroundState(QSize area) { _cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout); } } + generateNextBackgroundRotation(); return _backgroundState; } -void SessionController::cacheBackground() { - const auto background = Window::Theme::Background(); - if (background->colorForFill()) { +bool SessionController::readyForBackgroundRotation() const { + return !anim::Disabled() + && !_backgroundFade.animating() + && !_cacheBackgroundTimer.isActive() + && !_backgroundState.now.pixmap.isNull(); +} + +void SessionController::generateNextBackgroundRotation() { + if (_backgroundCachingRequest + || !_backgroundNext.image.isNull() + || !readyForBackgroundRotation()) { return; } + const auto background = Window::Theme::Background(); + if (background->paper().backgroundColors().size() < 3) { + return; + } + const auto request = currentCacheRequest(_backgroundState.now.area, 45); + if (!request) { + return; + } + cacheBackgroundAsync(request, [=](CacheBackgroundResult &&result) { + const auto forRequest = base::take(_backgroundCachingRequest); + if (!readyForBackgroundRotation()) { + return; + } + const auto request = currentCacheRequest( + _backgroundState.now.area, + 45); + if (forRequest == request) { + _backgroundAddRotation = (_backgroundAddRotation + 45) % 360; + _backgroundNext = std::move(result); + } + }); +} + +auto SessionController::currentCacheRequest(QSize area, int addRotation) const +-> CacheBackgroundRequest { + const auto background = Window::Theme::Background(); + if (background->colorForFill()) { + return {}; + } + const auto rotation = background->paper().gradientRotation(); + const auto gradient = background->gradientForFill(); + return { + .prepared = background->prepared(), + .preparedForTiled = background->preparedForTiled(), + .area = area, + .gradientRotation = (rotation + + _backgroundAddRotation + + addRotation) % 360, + .tile = background->tile(), + .recreateGradient = (addRotation != 0), + .gradient = gradient, + .gradientColors = (gradient.isNull() + ? std::vector() + : background->paper().backgroundColors()), + .gradientProgress = 1., + .patternOpacity = background->paper().patternOpacity(), + }; +} + +void SessionController::cacheBackground() { const auto now = crl::now(); if (now - _lastAreaChangeTime < kCacheBackgroundTimeout && QGuiApplication::mouseButtons() != 0) { _cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout); return; } - const auto gradient = background->gradientForFill(); - const auto patternOpacity = background->paper().patternOpacity(); - const auto &prepared = background->prepared(); - if (background->tile() || prepared.isNull()) { - auto result = gradient.isNull() - ? QImage( - _willCacheForArea * cIntRetinaFactor(), - QImage::Format_ARGB32_Premultiplied) - : gradient.scaled( - _willCacheForArea * cIntRetinaFactor(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - result.setDevicePixelRatio(cRetinaFactor()); - if (!prepared.isNull()) { - QPainter p(&result); - if (!gradient.isNull()) { - p.setCompositionMode(QPainter::CompositionMode_SoftLight); - p.setOpacity(patternOpacity); - } - const auto &tiled = background->preparedForTiled(); - const auto w = tiled.width() / cRetinaFactor(); - const auto h = tiled.height() / cRetinaFactor(); - auto sx = 0; - auto sy = 0; - const auto cx = qCeil(_willCacheForArea.width() / w); - const auto cy = qCeil(_willCacheForArea.height() / h); - for (int i = sx; i < cx; ++i) { - for (int j = sy; j < cy; ++j) { - p.drawImage(QPointF(i * w, j * h), tiled); - } - } + cacheBackgroundNow(); +} + +void SessionController::cacheBackgroundNow() { + if (!_backgroundCachingRequest) { + if (const auto request = currentCacheRequest(_willCacheForArea)) { + cacheBackgroundAsync(request); } - setCachedBackground({ - .pixmap = Ui::PixmapFromImage(std::move(result)), - .area = _willCacheForArea, - }); - } else { - const auto rects = Window::Theme::ComputeBackgroundRects( - _willCacheForArea, - prepared.size()); - auto image = prepared.copy(rects.from).scaled( - rects.to.width() * cIntRetinaFactor(), - rects.to.height() * cIntRetinaFactor(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - auto result = gradient.isNull() - ? std::move(image) - : gradient.scaled( - image.size(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - result.setDevicePixelRatio(cRetinaFactor()); - if (!gradient.isNull()) { - QPainter p(&result); - p.setCompositionMode(QPainter::CompositionMode_SoftLight); - p.setOpacity(patternOpacity); - p.drawImage(QRect(QPoint(), rects.to.size()), image); - } - image = QImage(); - setCachedBackground({ - .pixmap = Ui::PixmapFromImage(std::move(result)), - .area = _willCacheForArea, - .x = rects.to.x(), - .y = rects.to.y(), - }); } } -void SessionController::setCachedBackground(CachedBackground &&cached) { +void SessionController::cacheBackgroundAsync( + const CacheBackgroundRequest &request, + Fn done) { + _backgroundCachingRequest = request; + const auto weak = base::make_weak(this); + crl::async([=] { + if (!weak) { + return; + } + crl::on_main(weak, [=, result = CacheBackground(request)]() mutable { + if (done) { + done(std::move(result)); + } else if (const auto request = currentCacheRequest( + _willCacheForArea)) { + if (_backgroundCachingRequest != request) { + cacheBackgroundAsync(request); + } else { + _backgroundCachingRequest = {}; + setCachedBackground(std::move(result)); + } + } + }); + }); +} + +void SessionController::setCachedBackground(CacheBackgroundResult &&cached) { const auto background = Window::Theme::Background(); if (background->gradientForFill().isNull() || _backgroundState.now.pixmap.isNull()) { _backgroundFade.stop(); _backgroundState.shown = 1.; _backgroundState.now = std::move(cached); + _backgroundNext = {}; return; } // #TODO themes compose several transitions. @@ -1439,6 +1570,8 @@ void SessionController::setCachedBackground(CachedBackground &&cached) { void SessionController::clearCachedBackground() { _backgroundState = {}; + _backgroundAddRotation = 0; + _backgroundNext = {}; _backgroundFade.stop(); _cacheBackgroundTimer.cancel(); _repaintBackgroundRequests.fire({}); @@ -1448,6 +1581,14 @@ rpl::producer<> SessionController::repaintBackgroundRequests() const { return _repaintBackgroundRequests.events(); } +void SessionController::rotateComplexGradientBackground() { + if (!_backgroundFade.animating() && !_backgroundNext.image.isNull()) { + Window::Theme::Background()->recacheGradientForFill( + std::move(_backgroundNext.gradient)); + setCachedBackground(base::take(_backgroundNext)); + } +} + SessionController::~SessionController() { resetFakeUnreadWhileOpened(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 1274533ca..a30f7cb60 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -55,7 +55,42 @@ class SectionMemento; class Controller; class FiltersMenu; +struct CacheBackgroundRequest { + QImage prepared; + QImage preparedForTiled; + QSize area; + int gradientRotation = 0; + bool tile = false; + bool recreateGradient = false; + QImage gradient; + std::vector gradientColors; + float64 gradientProgress = 1.; + float64 patternOpacity = 1.; + + explicit operator bool() const { + return !prepared.isNull() || !gradient.isNull(); + } +}; + +bool operator==( + const CacheBackgroundRequest &a, + const CacheBackgroundRequest &b); +bool operator!=( + const CacheBackgroundRequest &a, + const CacheBackgroundRequest &b); + +struct CacheBackgroundResult { + QImage image; + QImage gradient; + QSize area; + int x = 0; + int y = 0; +}; + struct CachedBackground { + CachedBackground() = default; + CachedBackground(CacheBackgroundResult &&result); + QPixmap pixmap; QSize area; int x = 0; @@ -406,6 +441,7 @@ public: [[nodiscard]] const BackgroundState &backgroundState(QSize area); [[nodiscard]] rpl::producer<> repaintBackgroundRequests() const; + void rotateComplexGradientBackground(); rpl::lifetime &lifetime() { return _lifetime; @@ -437,8 +473,17 @@ private: void checkInvitePeek(); void cacheBackground(); + void cacheBackgroundNow(); + void cacheBackgroundAsync( + const CacheBackgroundRequest &request, + Fn done = nullptr); void clearCachedBackground(); - void setCachedBackground(CachedBackground &&cached); + void setCachedBackground(CacheBackgroundResult &&cached); + [[nodiscard]] CacheBackgroundRequest currentCacheRequest( + QSize area, + int addRotation = 0) const; + [[nodiscard]] bool readyForBackgroundRotation() const; + void generateNextBackgroundRotation(); const not_null _window; @@ -469,6 +514,9 @@ private: BackgroundState _backgroundState; Ui::Animations::Simple _backgroundFade; + CacheBackgroundRequest _backgroundCachingRequest; + CacheBackgroundResult _backgroundNext; + int _backgroundAddRotation = 0; QSize _willCacheForArea; crl::time _lastAreaChangeTime = 0; base::Timer _cacheBackgroundTimer;