Animated transition on pattern-on-gradient resize.

This commit is contained in:
John Preston 2021-08-13 21:18:06 +03:00
parent b9a9520ef5
commit 3dadcd9352
17 changed files with 213 additions and 135 deletions

View file

@ -26,7 +26,7 @@ object_ptr<Window::SectionWidget> TabbedMemento::createWidget(
TabbedSection::TabbedSection( TabbedSection::TabbedSection(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller, PaintedBackground::Custom)
, _selector(controller->tabbedSelector()) { , _selector(controller->tabbedSelector()) {
_selector->setParent(this); _selector->setParent(this);
_selector->setRoundRadius(0); _selector->setRoundRadius(0);

View file

@ -164,7 +164,7 @@ void Widget::BottomButton::paintEvent(QPaintEvent *e) {
Widget::Widget( Widget::Widget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller) : Window::AbstractSectionWidget(parent, controller, PaintedBackground::Custom)
, _api(&controller->session().mtp()) , _api(&controller->session().mtp())
, _searchControls(this) , _searchControls(this)
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle) , _mainMenuToggle(_searchControls, st::dialogsMenuToggle)

View file

@ -275,11 +275,14 @@ Widget::Widget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<ChannelData*> channel) not_null<ChannelData*> channel)
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller, PaintedBackground::Section)
, _scroll(this, st::historyScroll, false) , _scroll(this, st::historyScroll, false)
, _fixedBar(this, controller, channel) , _fixedBar(this, controller, channel)
, _fixedBarShadow(this) , _fixedBarShadow(this)
, _whatIsThis(this, tr::lng_admin_log_about(tr::now).toUpper(), st::historyComposeButton) { , _whatIsThis(
this,
tr::lng_admin_log_about(tr::now).toUpper(),
st::historyComposeButton) {
_fixedBar->move(0, 0); _fixedBar->move(0, 0);
_fixedBar->resizeToWidth(width()); _fixedBar->resizeToWidth(width());
_fixedBar->showFilterRequests( _fixedBar->showFilterRequests(
@ -303,6 +306,11 @@ Widget::Widget(
updateAdaptiveLayout(); updateAdaptiveLayout();
}, lifetime()); }, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel)); _inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel));
_inner->showSearchSignal( _inner->showSearchSignal(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {

View file

@ -171,7 +171,7 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
HistoryWidget::HistoryWidget( HistoryWidget::HistoryWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller) : Window::AbstractSectionWidget(parent, controller, PaintedBackground::Section)
, _api(&controller->session().mtp()) , _api(&controller->session().mtp())
, _updateEditTimeLeftDisplay([=] { updateField(); }) , _updateEditTimeLeftDisplay([=] { updateField(); })
, _fieldBarCancel(this, st::historyReplyCancel) , _fieldBarCancel(this, st::historyReplyCancel)
@ -432,6 +432,11 @@ HistoryWidget::HistoryWidget(
} }
}, lifetime()); }, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
session().data().newItemAdded( session().data().newItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) { ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
newItemAdded(item); newItemAdded(item);

View file

@ -90,7 +90,7 @@ PinnedWidget::PinnedWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history) not_null<History*> history)
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller, PaintedBackground::Section)
, _history(history->migrateToOrMe()) , _history(history->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom()) , _migratedPeer(_history->peer->migrateFrom())
, _topBar(this, controller) , _topBar(this, controller)

View file

@ -146,7 +146,7 @@ RepliesWidget::RepliesWidget(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history, not_null<History*> history,
MsgId rootId) MsgId rootId)
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller, PaintedBackground::Section)
, _history(history) , _history(history)
, _rootId(rootId) , _rootId(rootId)
, _root(lookupRoot()) , _root(lookupRoot())

View file

@ -90,7 +90,7 @@ ScheduledWidget::ScheduledWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history) not_null<History*> history)
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller, PaintedBackground::Section)
, _history(history) , _history(history)
, _scroll(this, st::historyScroll, false) , _scroll(this, st::historyScroll, false)
, _topBar(this, controller) , _topBar(this, controller)

View file

@ -24,7 +24,7 @@ SectionWidget::SectionWidget(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
Wrap wrap, Wrap wrap,
not_null<Memento*> memento) not_null<Memento*> memento)
: Window::SectionWidget(parent, window) : Window::SectionWidget(parent, window, PaintedBackground::Custom)
, _content(this, window, wrap, memento) { , _content(this, window, wrap, memento) {
init(); init();
} }
@ -34,7 +34,7 @@ SectionWidget::SectionWidget(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
Wrap wrap, Wrap wrap,
not_null<MoveMemento*> memento) not_null<MoveMemento*> memento)
: Window::SectionWidget(parent, window) : Window::SectionWidget(parent, window, PaintedBackground::Custom)
, _content(memento->takeContent(this, wrap)) { , _content(memento->takeContent(this, wrap)) {
init(); init();
} }

View file

@ -59,7 +59,7 @@ WrapWidget::WrapWidget(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
Wrap wrap, Wrap wrap,
not_null<Memento*> memento) not_null<Memento*> memento)
: SectionWidget(parent, window) : SectionWidget(parent, window, PaintedBackground::Custom)
, _wrap(wrap) , _wrap(wrap)
, _controller(createController(window, memento->content())) , _controller(createController(window, memento->content()))
, _topShadow(this) { , _topShadow(this) {

View file

@ -571,18 +571,21 @@ void BackgroundRow::updateImage() {
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
} }
const auto &pix = background->pixmap(); const auto &prepared = background->prepared();
if (!pix.isNull()) { if (!prepared.isNull()) {
const auto sx = (pix.width() > pix.height()) const auto sx = (prepared.width() > prepared.height())
? ((pix.width() - pix.height()) / 2) ? ((prepared.width() - prepared.height()) / 2)
: 0; : 0;
const auto sy = (pix.height() > pix.width()) const auto sy = (prepared.height() > prepared.width())
? ((pix.height() - pix.width()) / 2) ? ((prepared.height() - prepared.width()) / 2)
: 0; : 0;
const auto s = (pix.width() > pix.height()) const auto s = (prepared.width() > prepared.height())
? pix.height() ? prepared.height()
: pix.width(); : prepared.width();
p.drawPixmap(0, 0, size, size, pix, sx, sy, s, s); p.drawImage(
QRect(0, 0, size, size),
prepared,
QRect(sx, sy, s, s));
} }
} }
} }

View file

@ -18,14 +18,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Window { namespace Window {
AbstractSectionWidget::AbstractSectionWidget(
QWidget *parent,
not_null<SessionController*> controller,
PaintedBackground paintedBackground)
: RpWidget(parent)
, _controller(controller) {
if (paintedBackground == PaintedBackground::Section) {
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
}
}
Main::Session &AbstractSectionWidget::session() const { Main::Session &AbstractSectionWidget::session() const {
return _controller->session(); return _controller->session();
} }
SectionWidget::SectionWidget( SectionWidget::SectionWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller,
: AbstractSectionWidget(parent, controller) { PaintedBackground paintedBackground)
: AbstractSectionWidget(parent, controller, paintedBackground) {
} }
void SectionWidget::setGeometryWithTopMoved( void SectionWidget::setGeometryWithTopMoved(
@ -99,18 +114,16 @@ void SectionWidget::PaintBackground(
const auto gradient = background->gradientForFill(); const auto gradient = background->gradientForFill();
const auto fill = QSize(widget->width(), fullHeight); const auto fill = QSize(widget->width(), fullHeight);
auto fromy = controller->content()->backgroundFromY(); auto fromy = controller->content()->backgroundFromY();
auto cached = controller->cachedBackground(fill); auto state = controller->backgroundState(fill);
const auto goodCache = (cached.area == fill); const auto paintCache = [&](const CachedBackground &cache) {
const auto useCached = goodCache || !gradient.isNull();
if (!cached.pixmap.isNull()) {
const auto to = QRect( const auto to = QRect(
QPoint(cached.x, fromy + cached.y), QPoint(cache.x, fromy + cache.y),
cached.pixmap.size() / cIntRetinaFactor()); cache.pixmap.size() / cIntRetinaFactor());
if (goodCache) { if (cache.area == fill) {
p.drawPixmap(to, cached.pixmap); p.drawPixmap(to, cache.pixmap);
} else { } else {
const auto sx = fill.width() / float64(cached.area.width()); const auto sx = fill.width() / float64(cache.area.width());
const auto sy = fill.height() / float64(cached.area.height()); const auto sy = fill.height() / float64(cache.area.height());
const auto round = [](float64 value) -> int { const auto round = [](float64 value) -> int {
return (value >= 0.) return (value >= 0.)
? int(std::ceil(value)) ? int(std::ceil(value))
@ -122,17 +135,27 @@ void SectionWidget::PaintBackground(
sto.y(), sto.y(),
round((to.x() + to.width()) * sx) - sto.x(), round((to.x() + to.width()) * sx) - sto.x(),
round((to.y() + to.height()) * sy) - sto.y(), round((to.y() + to.height()) * sy) - sto.y(),
cached.pixmap); cache.pixmap);
} }
};
const auto goodNow = !state.now.pixmap.isNull()
&& (state.now.area == fill);
const auto useCache = goodNow || !gradient.isNull();
if (useCache) {
if (state.shown < 1. && !gradient.isNull()) {
paintCache(state.was);
p.setOpacity(state.shown);
}
paintCache(state.now);
return; return;
} }
const auto patternOpacity = background->paper().patternOpacity(); const auto patternOpacity = background->paper().patternOpacity();
const auto &bg = background->pixmap(); const auto &prepared = background->prepared();
if (!bg.isNull() && !background->tile()) { if (!prepared.isNull() && !background->tile()) {
auto hq = PainterHighQualityEnabler(p); const auto hq = PainterHighQualityEnabler(p);
const auto rects = Window::Theme::ComputeBackgroundRects( const auto rects = Window::Theme::ComputeBackgroundRects(
fill, fill,
bg.size()); prepared.size());
if (!gradient.isNull()) { if (!gradient.isNull()) {
p.drawImage(rects.to, gradient); p.drawImage(rects.to, gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
@ -140,17 +163,17 @@ void SectionWidget::PaintBackground(
} }
auto to = rects.to; auto to = rects.to;
to.moveTop(to.top() + fromy); to.moveTop(to.top() + fromy);
p.drawPixmap(to, bg, rects.from); p.drawImage(to, prepared, rects.from);
return; return;
} }
if (!gradient.isNull()) { if (!gradient.isNull()) {
auto hq = PainterHighQualityEnabler(p); const auto hq = PainterHighQualityEnabler(p);
p.drawImage(QRect(QPoint(0, fromy), fill), gradient); p.drawImage(QRect(QPoint(0, fromy), fill), gradient);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
} }
if (!bg.isNull()) { if (!prepared.isNull()) {
auto &tiled = background->pixmapForTiled(); const auto &tiled = background->preparedForTiled();
auto left = clip.left(); auto left = clip.left();
auto top = clip.top(); auto top = clip.top();
auto right = clip.left() + clip.width(); auto right = clip.left() + clip.width();
@ -163,7 +186,7 @@ void SectionWidget::PaintBackground(
auto cy = qCeil((bottom - fromy) / h); auto cy = qCeil((bottom - fromy) / h);
for (auto i = sx; i < cx; ++i) { for (auto i = sx; i < cx; ++i) {
for (auto j = sy; j < cy; ++j) { for (auto j = sy; j < cy; ++j) {
p.drawPixmap(QPointF(i * w, fromy + j * h), tiled); p.drawImage(QPointF(i * w, fromy + j * h), tiled);
} }
} }
} }

View file

@ -40,12 +40,14 @@ class AbstractSectionWidget
, public Media::Player::FloatSectionDelegate , public Media::Player::FloatSectionDelegate
, protected base::Subscriber { , protected base::Subscriber {
public: public:
enum class PaintedBackground {
Section,
Custom,
};
AbstractSectionWidget( AbstractSectionWidget(
QWidget *parent, QWidget *parent,
not_null<SessionController*> controller) not_null<SessionController*> controller,
: RpWidget(parent) PaintedBackground paintedBackground);
, _controller(controller) {
}
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<SessionController*> controller() const { [[nodiscard]] not_null<SessionController*> controller() const {
@ -84,7 +86,8 @@ class SectionWidget : public AbstractSectionWidget {
public: public:
SectionWidget( SectionWidget(
QWidget *parent, QWidget *parent,
not_null<SessionController*> controller); not_null<SessionController*> controller,
PaintedBackground paintedBackground);
virtual Dialogs::RowDescriptor activeChat() const { virtual Dialogs::RowDescriptor activeChat() const {
return {}; return {};

View file

@ -730,8 +730,8 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
Assert(colorForFill() Assert(colorForFill()
|| !_gradient.isNull() || !_gradient.isNull()
|| (!_original.isNull() || (!_original.isNull()
&& !_pixmap.isNull() && !_prepared.isNull()
&& !_pixmapForTiled.isNull())); && !_preparedForTiled.isNull()));
_updates.fire({ BackgroundUpdate::Type::New, tile() }); // delayed? _updates.fire({ BackgroundUpdate::Type::New, tile() }); // delayed?
if (needResetAdjustable) { if (needResetAdjustable) {
@ -763,46 +763,42 @@ void ChatBackground::setPrepared(
} }
_original = std::move(original); _original = std::move(original);
_prepared = std::move(prepared);
_gradient = std::move(gradient); _gradient = std::move(gradient);
preparePixmaps(std::move(prepared)); _imageMonoColor = _gradient.isNull()
? CalculateImageMonoColor(_prepared)
: std::nullopt;
prepareImageForTiled();
} }
void ChatBackground::preparePixmaps(QImage image) { void ChatBackground::prepareImageForTiled() {
const auto width = image.width(); const auto width = _prepared.width();
const auto height = image.height(); const auto height = _prepared.height();
const auto isSmallForTiled = (width > 0 && height > 0) const auto isSmallForTiled = (width > 0 && height > 0)
&& (width < kMinimumTiledSize || height < kMinimumTiledSize); && (width < kMinimumTiledSize || height < kMinimumTiledSize);
if (isSmallForTiled) {
const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width));
const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height));
auto imageForTiled = QImage(
width * repeatTimesX,
height * repeatTimesY,
QImage::Format_ARGB32_Premultiplied);
imageForTiled.setDevicePixelRatio(image.devicePixelRatio());
auto imageForTiledBytes = imageForTiled.bits();
auto bytesInLine = width * sizeof(uint32);
for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
auto imageBytes = image.constBits();
for (auto y = 0; y != height; ++y) {
for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
memcpy(imageForTiledBytes, imageBytes, bytesInLine);
imageForTiledBytes += bytesInLine;
}
imageBytes += image.bytesPerLine();
imageForTiledBytes += imageForTiled.bytesPerLine() - (repeatTimesX * bytesInLine);
}
}
_pixmapForTiled = Ui::PixmapFromImage(std::move(imageForTiled));
}
_imageMonoColor = _gradient.isNull()
? CalculateImageMonoColor(image)
: std::nullopt;
_pixmap = image.isNull()
? QPixmap()
: Ui::PixmapFromImage(std::move(image));
if (!isSmallForTiled) { if (!isSmallForTiled) {
_pixmapForTiled = _pixmap; _preparedForTiled = _prepared;
return;
}
const auto repeatTimesX = qCeil(kMinimumTiledSize / (1. * width));
const auto repeatTimesY = qCeil(kMinimumTiledSize / (1. * height));
_preparedForTiled = QImage(
width * repeatTimesX,
height * repeatTimesY,
QImage::Format_ARGB32_Premultiplied);
_preparedForTiled.setDevicePixelRatio(_prepared.devicePixelRatio());
auto imageForTiledBytes = _preparedForTiled.bits();
auto bytesInLine = width * sizeof(uint32);
for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
auto imageBytes = _prepared.constBits();
for (auto y = 0; y != height; ++y) {
for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
memcpy(imageForTiledBytes, imageBytes, bytesInLine);
imageForTiledBytes += bytesInLine;
}
imageBytes += _prepared.bytesPerLine();
imageForTiledBytes += _preparedForTiled.bytesPerLine() - (repeatTimesX * bytesInLine);
}
} }
} }
@ -868,7 +864,7 @@ void ChatBackground::adjustPaletteUsingColor(QColor color) {
} }
std::optional<QColor> ChatBackground::colorForFill() const { std::optional<QColor> ChatBackground::colorForFill() const {
return !_pixmap.isNull() return !_prepared.isNull()
? imageMonoColor() ? imageMonoColor()
: !_gradient.isNull() : !_gradient.isNull()
? std::nullopt ? std::nullopt
@ -888,13 +884,12 @@ QImage ChatBackground::createCurrentImage() const {
result.fill(*fill); result.fill(*fill);
return result; return result;
} else if (_gradient.isNull()) { } else if (_gradient.isNull()) {
return pixmap().toImage(); return _prepared;
} else if (pixmap().isNull()) { } else if (_prepared.isNull()) {
return _gradient; return _gradient;
} }
const auto &pattern = pixmap();
auto result = _gradient.scaled( auto result = _gradient.scaled(
pattern.size(), _prepared.size(),
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
result.setDevicePixelRatio(1.); result.setDevicePixelRatio(1.);
@ -902,7 +897,7 @@ QImage ChatBackground::createCurrentImage() const {
auto p = QPainter(&result); auto p = QPainter(&result);
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(paper().patternOpacity()); p.setOpacity(paper().patternOpacity());
p.drawPixmap(QRect(QPoint(), pattern.size()), pattern); p.drawImage(QRect(QPoint(), _prepared.size()), _prepared);
} }
return result; return result;
} }
@ -1044,7 +1039,7 @@ void ChatBackground::setTestingTheme(Instance &&theme) {
saveForRevert(); saveForRevert();
set( set(
Data::details::TestingEditorWallPaper(), Data::details::TestingEditorWallPaper(),
std::move(_pixmap).toImage()); base::take(_prepared));
} }
} else if (switchToThemeBackground) { } else if (switchToThemeBackground) {
saveForRevert(); saveForRevert();

View file

@ -170,11 +170,11 @@ public:
[[nodiscard]] WallPaperId id() const { [[nodiscard]] WallPaperId id() const {
return _paper.id(); return _paper.id();
} }
[[nodiscard]] const QPixmap &pixmap() const { [[nodiscard]] const QImage &prepared() const {
return _pixmap; return _prepared;
} }
[[nodiscard]] const QPixmap &pixmapForTiled() const { [[nodiscard]] const QImage &preparedForTiled() const {
return _pixmapForTiled; return _preparedForTiled;
} }
[[nodiscard]] std::optional<QColor> colorForFill() const; [[nodiscard]] std::optional<QColor> colorForFill() const;
[[nodiscard]] QImage gradientForFill() const; [[nodiscard]] QImage gradientForFill() const;
@ -197,7 +197,7 @@ private:
void initialRead(); void initialRead();
void saveForRevert(); void saveForRevert();
void setPrepared(QImage original, QImage prepared, QImage gradient); void setPrepared(QImage original, QImage prepared, QImage gradient);
void preparePixmaps(QImage image); void prepareImageForTiled();
void writeNewBackgroundSettings(); void writeNewBackgroundSettings();
void setPaper(const Data::WallPaper &paper); void setPaper(const Data::WallPaper &paper);
@ -239,8 +239,8 @@ private:
std::optional<QColor> _paperColor; std::optional<QColor> _paperColor;
QImage _gradient; QImage _gradient;
QImage _original; QImage _original;
QPixmap _pixmap; QImage _prepared;
QPixmap _pixmapForTiled; QImage _preparedForTiled;
bool _nightMode = false; bool _nightMode = false;
bool _tileDayValue = false; bool _tileDayValue = false;
bool _tileNightValue = true; bool _tileNightValue = true;

View file

@ -988,10 +988,11 @@ void MainMenu::refreshBackground() {
const auto intensityText = IntensityOfColor(st::mainMenuCoverFg->c); const auto intensityText = IntensityOfColor(st::mainMenuCoverFg->c);
const auto background = Window::Theme::Background(); const auto background = Window::Theme::Background();
const auto &paper = background->paper(); const auto &paper = background->paper();
const auto &pixmap = background->pixmap(); const auto &prepared = background->prepared();
QRect to, from; const auto rects = Window::Theme::ComputeBackgroundRects(
const auto rects = Window::Theme::ComputeBackgroundRects(fill, pixmap.size()); fill,
prepared.size());
auto backgroundImage = !paper.backgroundColors().empty() auto backgroundImage = !paper.backgroundColors().empty()
? Data::GenerateWallPaper( ? Data::GenerateWallPaper(
@ -999,7 +1000,7 @@ void MainMenu::refreshBackground() {
paper.backgroundColors(), paper.backgroundColors(),
paper.gradientRotation(), paper.gradientRotation(),
paper.patternOpacity(), paper.patternOpacity(),
[&](QPainter &p) { p.drawPixmap(to, pixmap, from); }) [&](QPainter &p) { p.drawImage(rects.to, prepared, rects.from); })
: QImage( : QImage(
fill * cIntRetinaFactor(), fill * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
@ -1029,7 +1030,7 @@ void MainMenu::refreshBackground() {
// Background image. // Background image.
if (!paper.isPattern()) { if (!paper.isPattern()) {
p.drawPixmap(to, pixmap, from); p.drawImage(rects.to, prepared, rects.from);
} }
// Cut off the part of the background that is under text. // Cut off the part of the background that is under text.

View file

@ -72,6 +72,7 @@ namespace {
constexpr auto kMaxChatEntryHistorySize = 50; constexpr auto kMaxChatEntryHistorySize = 50;
constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000); constexpr auto kCacheBackgroundTimeout = 3 * crl::time(1000);
constexpr auto kCacheBackgroundFastTimeout = crl::time(200); constexpr auto kCacheBackgroundFastTimeout = crl::time(200);
constexpr auto kBackgroundFadeDuration = crl::time(200);
} // namespace } // namespace
@ -1318,15 +1319,16 @@ void SessionController::openDocument(
session().data().message(contextId)); session().data().message(contextId));
} }
CachedBackground SessionController::cachedBackground(QSize area) { const BackgroundState &SessionController::backgroundState(QSize area) {
if (_cachedBackground.area != area) { _backgroundState.shown = _backgroundFade.value(1.);
if (_backgroundState.now.area != area) {
if (_willCacheForArea != area || !_cacheBackgroundTimer.isActive()) { if (_willCacheForArea != area || !_cacheBackgroundTimer.isActive()) {
_willCacheForArea = area; _willCacheForArea = area;
_lastAreaChangeTime = crl::now(); _lastAreaChangeTime = crl::now();
_cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout); _cacheBackgroundTimer.callOnce(kCacheBackgroundFastTimeout);
} }
} }
return _cachedBackground; return _backgroundState;
} }
void SessionController::cacheBackground() { void SessionController::cacheBackground() {
@ -1342,8 +1344,8 @@ void SessionController::cacheBackground() {
} }
const auto gradient = background->gradientForFill(); const auto gradient = background->gradientForFill();
const auto patternOpacity = background->paper().patternOpacity(); const auto patternOpacity = background->paper().patternOpacity();
const auto &bg = background->pixmap(); const auto &prepared = background->prepared();
if (background->tile() || bg.isNull()) { if (background->tile() || prepared.isNull()) {
auto result = gradient.isNull() auto result = gradient.isNull()
? QImage( ? QImage(
_willCacheForArea * cIntRetinaFactor(), _willCacheForArea * cIntRetinaFactor(),
@ -1353,34 +1355,34 @@ void SessionController::cacheBackground() {
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor()); result.setDevicePixelRatio(cRetinaFactor());
if (!bg.isNull()) { if (!prepared.isNull()) {
QPainter p(&result); QPainter p(&result);
if (!gradient.isNull()) { if (!gradient.isNull()) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight); p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity); p.setOpacity(patternOpacity);
} }
const auto &tiled = background->pixmapForTiled(); const auto &tiled = background->preparedForTiled();
auto w = tiled.width() / cRetinaFactor(); const auto w = tiled.width() / cRetinaFactor();
auto h = tiled.height() / cRetinaFactor(); const auto h = tiled.height() / cRetinaFactor();
auto sx = 0; auto sx = 0;
auto sy = 0; auto sy = 0;
auto cx = qCeil(_willCacheForArea.width() / w); const auto cx = qCeil(_willCacheForArea.width() / w);
auto cy = qCeil(_willCacheForArea.height() / h); const auto cy = qCeil(_willCacheForArea.height() / h);
for (int i = sx; i < cx; ++i) { for (int i = sx; i < cx; ++i) {
for (int j = sy; j < cy; ++j) { for (int j = sy; j < cy; ++j) {
p.drawPixmap(QPointF(i * w, j * h), tiled); p.drawImage(QPointF(i * w, j * h), tiled);
} }
} }
} }
_cachedBackground = CachedBackground{ setCachedBackground({
.pixmap = Ui::PixmapFromImage(std::move(result)), .pixmap = Ui::PixmapFromImage(std::move(result)),
.area = _willCacheForArea, .area = _willCacheForArea,
}; });
} else { } else {
const auto rects = Window::Theme::ComputeBackgroundRects( const auto rects = Window::Theme::ComputeBackgroundRects(
_willCacheForArea, _willCacheForArea,
bg.size()); prepared.size());
auto image = bg.toImage().copy(rects.from).scaled( auto image = prepared.copy(rects.from).scaled(
rects.to.width() * cIntRetinaFactor(), rects.to.width() * cIntRetinaFactor(),
rects.to.height() * cIntRetinaFactor(), rects.to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
@ -1399,21 +1401,51 @@ void SessionController::cacheBackground() {
p.drawImage(QRect(QPoint(), rects.to.size()), image); p.drawImage(QRect(QPoint(), rects.to.size()), image);
} }
image = QImage(); image = QImage();
_cachedBackground = { setCachedBackground({
.pixmap = Ui::PixmapFromImage(std::move(result)), .pixmap = Ui::PixmapFromImage(std::move(result)),
.area = _willCacheForArea, .area = _willCacheForArea,
.x = rects.to.x(), .x = rects.to.x(),
.y = rects.to.y(), .y = rects.to.y(),
}; });
}
if (!gradient.isNull()) {
content()->update();
} }
} }
void SessionController::setCachedBackground(CachedBackground &&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);
return;
}
// #TODO themes compose several transitions.
_backgroundState.was = std::move(_backgroundState.now);
_backgroundState.now = std::move(cached);
_backgroundState.shown = 0.;
const auto callback = [=] {
if (!_backgroundFade.animating()) {
_backgroundState.was = {};
_backgroundState.shown = 1.;
}
_repaintBackgroundRequests.fire({});
};
_backgroundFade.start(
callback,
0.,
1.,
kBackgroundFadeDuration);
}
void SessionController::clearCachedBackground() { void SessionController::clearCachedBackground() {
_cachedBackground = {}; _backgroundState = {};
_backgroundFade.stop();
_cacheBackgroundTimer.cancel(); _cacheBackgroundTimer.cancel();
_repaintBackgroundRequests.fire({});
}
rpl::producer<> SessionController::repaintBackgroundRequests() const {
return _repaintBackgroundRequests.events();
} }
SessionController::~SessionController() { SessionController::~SessionController() {

View file

@ -7,13 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include <rpl/variable.h>
#include "base/flags.h" #include "base/flags.h"
#include "base/object_ptr.h" #include "base/object_ptr.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "base/timer.h" #include "base/timer.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "ui/effects/animation_value.h" #include "ui/effects/animations.h"
#include "ui/layers/layer_widget.h" #include "ui/layers/layer_widget.h"
#include "window/window_adaptive.h" #include "window/window_adaptive.h"
@ -63,6 +62,12 @@ struct CachedBackground {
int y = 0; int y = 0;
}; };
struct BackgroundState {
CachedBackground was;
CachedBackground now;
float64 shown = 1.;
};
enum class GifPauseReason { enum class GifPauseReason {
Any = 0, Any = 0,
InlineResults = (1 << 0), InlineResults = (1 << 0),
@ -232,7 +237,6 @@ private:
MsgId _showingRepliesRootId = 0; MsgId _showingRepliesRootId = 0;
mtpRequestId _showingRepliesRequestId = 0; mtpRequestId _showingRepliesRequestId = 0;
}; };
class SessionController : public SessionNavigation { class SessionController : public SessionNavigation {
@ -296,11 +300,11 @@ public:
void floatPlayerAreaUpdated(); void floatPlayerAreaUpdated();
struct ColumnLayout { struct ColumnLayout {
int bodyWidth; int bodyWidth = 0;
int dialogsWidth; int dialogsWidth = 0;
int chatWidth; int chatWidth = 0;
int thirdWidth; int thirdWidth = 0;
Adaptive::WindowLayout windowLayout; Adaptive::WindowLayout windowLayout = Adaptive::WindowLayout();
}; };
[[nodiscard]] ColumnLayout computeColumnLayout() const; [[nodiscard]] ColumnLayout computeColumnLayout() const;
int dialogsSmallColumnWidth() const; int dialogsSmallColumnWidth() const;
@ -400,7 +404,8 @@ public:
void toggleFiltersMenu(bool enabled); void toggleFiltersMenu(bool enabled);
[[nodiscard]] rpl::producer<> filtersMenuChanged() const; [[nodiscard]] rpl::producer<> filtersMenuChanged() const;
[[nodiscard]] CachedBackground cachedBackground(QSize area); [[nodiscard]] const BackgroundState &backgroundState(QSize area);
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
rpl::lifetime &lifetime() { rpl::lifetime &lifetime() {
return _lifetime; return _lifetime;
@ -433,6 +438,7 @@ private:
void cacheBackground(); void cacheBackground();
void clearCachedBackground(); void clearCachedBackground();
void setCachedBackground(CachedBackground &&cached);
const not_null<Controller*> _window; const not_null<Controller*> _window;
@ -461,10 +467,12 @@ private:
rpl::event_stream<> _filtersMenuChanged; rpl::event_stream<> _filtersMenuChanged;
CachedBackground _cachedBackground; BackgroundState _backgroundState;
Ui::Animations::Simple _backgroundFade;
QSize _willCacheForArea; QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0; crl::time _lastAreaChangeTime = 0;
base::Timer _cacheBackgroundTimer; base::Timer _cacheBackgroundTimer;
rpl::event_stream<> _repaintBackgroundRequests;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;