diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index b3df51177..f97cac78f 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -431,17 +431,20 @@ ChatPaintContext ChatTheme::preparePaintContext( not_null st, QRect viewport, QRect clip) { - _bubblesBackground.area = viewport.size(); - //if (!_bubblesBackgroundPrepared.isNull() - // && _bubblesBackground.area != viewport.size() - // && !viewport.isEmpty()) { - // // #TODO bubbles delayed caching - // _bubblesBackground = CacheBackground({ - // .prepared = _bubblesBackgroundPrepared, - // .area = viewport.size(), - // }); - // _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; - //} + const auto area = viewport.size(); + if (!_bubblesBackgroundPrepared.isNull() + && _bubblesBackground.area != area) { + if (!_cacheBubblesTimer) { + _cacheBubblesTimer.emplace([=] { cacheBubbles(); }); + } + if (_cacheBubblesArea != area + || (!_cacheBubblesTimer->isActive() + && !_bubblesCachingRequest)) { + _cacheBubblesArea = area; + _lastBubblesAreaChangeTime = crl::now(); + _cacheBubblesTimer->callOnce(kCacheBackgroundFastTimeout); + } + } return { .st = st, .bubblesPattern = _bubblesBackgroundPattern.get(), @@ -460,15 +463,15 @@ const BackgroundState &ChatTheme::backgroundState(QSize area) { && !background().gradientForFill.isNull()) { // We don't support direct painting of patterned gradients. // So we need to sync-generate cache image here. - _willCacheForArea = area; - setCachedBackground(CacheBackground(currentCacheRequest(area))); + _cacheBackgroundArea = area; + setCachedBackground(CacheBackground(cacheBackgroundRequest(area))); _cacheBackgroundTimer->cancel(); } else if (_backgroundState.now.area != area) { - if (_willCacheForArea != area + if (_cacheBackgroundArea != area || (!_cacheBackgroundTimer->isActive() && !_backgroundCachingRequest)) { - _willCacheForArea = area; - _lastAreaChangeTime = crl::now(); + _cacheBackgroundArea = area; + _lastBackgroundAreaChangeTime = crl::now(); _cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout); } } @@ -495,7 +498,7 @@ void ChatTheme::generateNextBackgroundRotation() { return; } constexpr auto kAddRotationDoubled = (720 - 45); - const auto request = currentCacheRequest( + const auto request = cacheBackgroundRequest( _backgroundState.now.area, kAddRotationDoubled); if (!request) { @@ -506,7 +509,7 @@ void ChatTheme::generateNextBackgroundRotation() { if (!readyForBackgroundRotation()) { return; } - const auto request = currentCacheRequest( + const auto request = cacheBackgroundRequest( _backgroundState.now.area, kAddRotationDoubled); if (forRequest == request) { @@ -518,7 +521,7 @@ void ChatTheme::generateNextBackgroundRotation() { }); } -auto ChatTheme::currentCacheRequest(QSize area, int addRotation) const +auto ChatTheme::cacheBackgroundRequest(QSize area, int addRotation) const -> CacheBackgroundRequest { if (background().colorForFill) { return {}; @@ -527,7 +530,6 @@ auto ChatTheme::currentCacheRequest(QSize area, int addRotation) const .background = background(), .area = area, .gradientRotationAdd = addRotation, -// .recreateGradient = (addRotation != 0), }; } @@ -535,7 +537,7 @@ void ChatTheme::cacheBackground() { Expects(_cacheBackgroundTimer.has_value()); const auto now = crl::now(); - if (now - _lastAreaChangeTime < kCacheBackgroundTimeout + if (now - _lastBackgroundAreaChangeTime < kCacheBackgroundTimeout && QGuiApplication::mouseButtons() != 0) { _cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout); return; @@ -545,7 +547,8 @@ void ChatTheme::cacheBackground() { void ChatTheme::cacheBackgroundNow() { if (!_backgroundCachingRequest) { - if (const auto request = currentCacheRequest(_willCacheForArea)) { + if (const auto request = cacheBackgroundRequest( + _cacheBackgroundArea)) { cacheBackgroundAsync(request); } } @@ -563,8 +566,8 @@ void ChatTheme::cacheBackgroundAsync( crl::on_main(weak, [=, result = CacheBackground(request)]() mutable { if (done) { done(std::move(result)); - } else if (const auto request = currentCacheRequest( - _willCacheForArea)) { + } else if (const auto request = cacheBackgroundRequest( + _cacheBackgroundArea)) { if (_backgroundCachingRequest != request) { cacheBackgroundAsync(request); } else { @@ -605,6 +608,64 @@ void ChatTheme::setCachedBackground(CacheBackgroundResult &&cached) { kBackgroundFadeDuration); } +auto ChatTheme::cacheBubblesRequest(QSize area) const +-> CacheBackgroundRequest { + if (_bubblesBackgroundPrepared.isNull()) { + return {}; + } + return { + .background = { + .gradientForFill = _bubblesBackgroundPrepared, + }, + .area = area, + }; +} + +void ChatTheme::cacheBubbles() { + Expects(_cacheBubblesTimer.has_value()); + + const auto now = crl::now(); + if (now - _lastBubblesAreaChangeTime < kCacheBackgroundTimeout + && QGuiApplication::mouseButtons() != 0) { + _cacheBubblesTimer->callOnce(kCacheBackgroundFastTimeout); + return; + } + cacheBubblesNow(); +} + +void ChatTheme::cacheBubblesNow() { + if (!_bubblesCachingRequest) { + if (const auto request = cacheBackgroundRequest( + _cacheBubblesArea)) { + cacheBubblesAsync(request); + } + } +} + +void ChatTheme::cacheBubblesAsync( + const CacheBackgroundRequest &request) { + _bubblesCachingRequest = request; + const auto weak = base::make_weak(this); + crl::async([=] { + if (!weak) { + return; + } + crl::on_main(weak, [=, result = CacheBackground(request)]() mutable { + if (const auto request = cacheBubblesRequest( + _cacheBubblesArea)) { + if (_bubblesCachingRequest != request) { + cacheBubblesAsync(request); + } else { + _bubblesCachingRequest = {}; + _bubblesBackground = std::move(result); + _bubblesBackgroundPattern->pixmap + = _bubblesBackground.pixmap; + } + } + }); + }); +} + rpl::producer<> ChatTheme::repaintBackgroundRequests() const { return _repaintBackgroundRequests.events(); } diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index ca8c1004e..75bb4c787 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -147,12 +147,20 @@ private: const CacheBackgroundRequest &request, Fn done = nullptr); void setCachedBackground(CacheBackgroundResult &&cached); - [[nodiscard]] CacheBackgroundRequest currentCacheRequest( + [[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest( QSize area, int addRotation = 0) const; [[nodiscard]] bool readyForBackgroundRotation() const; void generateNextBackgroundRotation(); + void cacheBubbles(); + void cacheBubblesNow(); + void cacheBubblesAsync( + const CacheBackgroundRequest &request); + void setCachedBubbles(CacheBackgroundResult &&cached); + [[nodiscard]] CacheBackgroundRequest cacheBubblesRequest( + QSize area) const; + [[nodiscard]] style::colorizer bubblesAccentColorizer( const QColor &accent) const; void adjustPalette(const ChatThemeDescriptor &descriptor); @@ -167,11 +175,16 @@ private: Animations::Simple _backgroundFade; CacheBackgroundRequest _backgroundCachingRequest; CacheBackgroundResult _backgroundNext; - QSize _willCacheForArea; - crl::time _lastAreaChangeTime = 0; + QSize _cacheBackgroundArea; + crl::time _lastBackgroundAreaChangeTime = 0; std::optional _cacheBackgroundTimer; + CachedBackground _bubblesBackground; QImage _bubblesBackgroundPrepared; + CacheBackgroundRequest _bubblesCachingRequest; + QSize _cacheBubblesArea; + crl::time _lastBubblesAreaChangeTime = 0; + std::optional _cacheBubblesTimer; std::unique_ptr _bubblesBackgroundPattern; rpl::event_stream<> _repaintBackgroundRequests; diff --git a/Telegram/SourceFiles/ui/chat/message_bubble.cpp b/Telegram/SourceFiles/ui/chat/message_bubble.cpp index 08c62ce8a..47a899817 100644 --- a/Telegram/SourceFiles/ui/chat/message_bubble.cpp +++ b/Telegram/SourceFiles/ui/chat/message_bubble.cpp @@ -93,13 +93,11 @@ void PaintPatternBubble(Painter &p, const SimpleBubble &args) { const auto fillBg = [&](const QRect &rect) { const auto fill = rect.intersected(args.patternViewport); if (!fill.isEmpty()) { - p.setClipRect(fill); PaintPatternBubblePart( p, args.patternViewport, pattern->pixmap, fill); - p.setClipping(false); } }; const auto fillSh = [&](const QRect &rect) { @@ -346,10 +344,30 @@ void PaintPatternBubblePart( const QRect &viewport, const QPixmap &pixmap, const QRect &target) { - // #TODO bubbles optimizes - const auto to = viewport; - const auto from = QRect(QPoint(), pixmap.size()); - p.drawPixmap(to, pixmap, from); + const auto factor = pixmap.devicePixelRatio(); + if (viewport.size() * factor == pixmap.size()) { + const auto fill = target.intersected(viewport); + if (fill.isEmpty()) { + return; + } + p.drawPixmap(fill, pixmap, QRect( + (fill.topLeft() - viewport.topLeft()) * factor, + fill.size() * factor)); + } else { + const auto to = viewport; + const auto from = QRect(QPoint(), pixmap.size()); + const auto deviceRect = QRect( + QPoint(), + QSize(p.device()->width(), p.device()->height())); + const auto clip = (target != deviceRect); + if (clip) { + p.setClipRect(target); + } + p.drawPixmap(to, pixmap, from); + if (clip) { + p.setClipping(false); + } + } } void PaintPatternBubblePart( @@ -392,7 +410,9 @@ void PaintPatternBubblePart( QImage &cache) { Expects(paintContent != nullptr); - if (cache.size() != target.size() * style::DevicePixelRatio()) { + const auto targetOrigin = target.topLeft(); + const auto targetSize = target.size(); + if (cache.size() != targetSize * style::DevicePixelRatio()) { cache = QImage( target.size() * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); @@ -400,10 +420,15 @@ void PaintPatternBubblePart( } cache.fill(Qt::transparent); auto q = Painter(&cache); - q.translate(-target.topLeft()); + q.translate(-targetOrigin); paintContent(q); + q.translate(targetOrigin); q.setCompositionMode(QPainter::CompositionMode_SourceIn); - PaintPatternBubblePart(q, viewport, pixmap, target); + PaintPatternBubblePart( + q, + viewport.translated(-targetOrigin), + pixmap, + QRect(QPoint(), targetSize)); q.end(); p.drawImage(target, cache);