Skip to content

Commit 76cc59a

Browse files
committed
Show gift sticker in the background.
1 parent e792088 commit 76cc59a

File tree

5 files changed

+161
-25
lines changed

5 files changed

+161
-25
lines changed

Telegram/SourceFiles/ui/chat/chat_theme.cpp

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,27 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
6161
const auto b = int(base::SafeRound(symbol.y() * 255));
6262
const auto value = uint16((uint16(a) << 8) | uint16(b));
6363
const auto shuffled = uint16(value << 5) | uint16(value >> 3);
64-
return (shuffled % 90) - 45;
64+
return (shuffled % 60) - 30;
65+
}
66+
67+
[[nodiscard]] int ChooseGiftSymbolSkip(const std::vector<QRectF> &symbols) {
68+
if (symbols.empty()) {
69+
return -1;
70+
}
71+
auto maxIndex = -1;
72+
auto maxValue = 0.;
73+
for (auto i = 0, count = int(symbols.size()); i != count; ++i) {
74+
const auto &symbol = symbols[i];
75+
const auto center = symbol.center();
76+
const auto value = std::min(center.x(), 1. - center.x())
77+
* std::min(center.y(), 1. - center.y())
78+
* std::min(symbol.width(), symbol.height());
79+
if (maxIndex < 0 || maxValue < value) {
80+
maxIndex = i;
81+
maxValue = value;
82+
}
83+
}
84+
return maxIndex;
6585
}
6686

6787
[[nodiscard]] CacheBackgroundResult CacheBackgroundByRequest(
@@ -90,6 +110,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
90110
Qt::IgnoreAspectRatio,
91111
Qt::SmoothTransformation);
92112
result.setDevicePixelRatio(ratio);
113+
auto giftArea = QRect();
114+
int giftRotation = 0;
93115
if (!request.background.prepared.isNull()) {
94116
QPainter p(&result);
95117
if (!gradient.isNull()) {
@@ -115,27 +137,37 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
115137
const auto h = tiled.height() / float(ratio);
116138

117139
const auto &giftSymbols = request.background.giftSymbols;
140+
const auto giftSymbolsCount = int(giftSymbols.size());
141+
const auto giftSymbolSkip = ChooseGiftSymbolSkip(giftSymbols);
118142
const auto &giftSymbolFrame = request.background.giftSymbolFrame;
119143
const auto giftSymbolSize = giftSymbolFrame.size() / ratio;
144+
const auto giftSymbolRect = [&](const QRectF &symbol) {
145+
return QRectF(
146+
symbol.x() * w,
147+
symbol.y() * h,
148+
symbol.width() * w,
149+
symbol.height() * h);
150+
};
151+
const auto giftSymbolPaint = [&](QPainter &p, QRectF symbol) {
152+
const auto rect = giftSymbolRect(symbol);
153+
p.save();
154+
p.translate(rect.center());
155+
p.scale(
156+
rect.width() / giftSymbolSize.width(),
157+
rect.height() / giftSymbolSize.height());
158+
p.rotate(RotationForSymbol(symbol));
159+
p.translate(-rect.center());
160+
p.drawImage(rect.topLeft(), giftSymbolFrame);
161+
p.restore();
162+
};
120163
if (hasGiftSymbols) {
121164
auto q = QPainter(&tiled);
122165
auto hq = PainterHighQualityEnabler(q);
123166
q.setOpacity(0.8);
124-
for (const auto &symbol : giftSymbols) {
125-
const auto rect = QRectF(
126-
symbol.x() * w,
127-
symbol.y() * h,
128-
symbol.width() * w,
129-
symbol.height() * h);
130-
q.save();
131-
q.translate(rect.center());
132-
q.scale(
133-
rect.width() / giftSymbolSize.width(),
134-
rect.height() / giftSymbolSize.height());
135-
q.rotate(RotationForSymbol(symbol));
136-
q.translate(-rect.center());
137-
q.drawImage(rect.topLeft(), giftSymbolFrame);
138-
q.restore();
167+
for (auto i = 0; i != giftSymbolsCount; ++i) {
168+
if (i != giftSymbolSkip) {
169+
giftSymbolPaint(q, giftSymbols[i]);
170+
}
139171
}
140172
}
141173
const auto cx = int(std::ceil(request.area.width() / w));
@@ -148,10 +180,33 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
148180
? (request.area.width() * ratio - cols * tiled.width()) / 2
149181
: 0;
150182
const auto useshift = xshift / float(ratio);
183+
const auto drawTile = [&](int x, int y) {
184+
const auto position = QPointF(useshift + x * w, y * h);
185+
p.drawImage(position, tiled);
186+
};
187+
188+
// Skip a symbol in the center for the gift itself.
189+
drawTile(cols / 2, 0);
190+
if (hasGiftSymbols) {
191+
const auto &gift = giftSymbols[giftSymbolSkip];
192+
auto q = QPainter(&tiled);
193+
auto hq = PainterHighQualityEnabler(q);
194+
q.setOpacity(0.8);
195+
giftSymbolPaint(q, gift);
196+
const auto exact = giftSymbolRect(gift);
197+
giftArea = QRect(
198+
useshift + (cols / 2) * w + exact.x(),
199+
exact.y(),
200+
exact.width(),
201+
exact.height());
202+
giftRotation = RotationForSymbol(gift);
203+
}
204+
151205
for (auto y = 0; y != rows; ++y) {
152206
for (auto x = 0; x != cols; ++x) {
153-
const auto position = QPointF(useshift + x * w, y * h);
154-
p.drawImage(position, tiled);
207+
if (y || x != (cols / 2)) {
208+
drawTile(x, y);
209+
}
155210
}
156211
}
157212
if (!gradient.isNull()
@@ -167,6 +222,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
167222
QImage::Format_ARGB32_Premultiplied),
168223
.gradient = gradient,
169224
.area = request.area,
225+
.giftArea = giftArea,
226+
.giftRotation = giftRotation,
170227
.waitingForNegativePattern
171228
= request.background.waitingForNegativePattern()
172229
};
@@ -239,6 +296,8 @@ CachedBackground::CachedBackground(CacheBackgroundResult &&result)
239296
, area(result.area)
240297
, x(result.x)
241298
, y(result.y)
299+
, giftArea(result.giftArea)
300+
, giftRotation(result.giftRotation)
242301
, waitingForNegativePattern(result.waitingForNegativePattern) {
243302
}
244303

@@ -456,6 +515,7 @@ void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
456515
_mutableBackground.key = background.key;
457516
_mutableBackground.prepared = std::move(background.prepared);
458517
_mutableBackground.giftSymbols = std::move(background.giftSymbols);
518+
_mutableBackground.giftId = background.giftId;
459519
_mutableBackground.preparedForTiled = std::move(
460520
background.preparedForTiled);
461521
if (!_backgroundState.now.pixmap.isNull()) {
@@ -1184,6 +1244,7 @@ ChatThemeBackground PrepareBackgroundImage(
11841244
.colors = data.colors,
11851245
.giftSymbols = std::move(read.giftSymbols),
11861246
.giftSymbolFrame = data.giftSymbolFrame,
1247+
.giftId = data.giftId,
11871248
.patternOpacity = data.patternOpacity,
11881249
.gradientRotation = data.generateGradient ? data.gradientRotation : 0,
11891250
.isPattern = data.isPattern,

Telegram/SourceFiles/ui/chat/chat_theme.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct ChatThemeBackground {
3535
std::vector<QColor> colors;
3636
std::vector<QRectF> giftSymbols;
3737
QImage giftSymbolFrame;
38+
uint64_t giftId = 0;
3839
float64 patternOpacity = 1.;
3940
int gradientRotation = 0;
4041
bool isPattern = false;
@@ -53,6 +54,7 @@ struct ChatThemeBackgroundData {
5354
QString path;
5455
QByteArray bytes;
5556
QImage giftSymbolFrame;
57+
uint64 giftId = 0;
5658
bool gzipSvg = false;
5759
std::vector<QColor> colors;
5860
bool isPattern = false;
@@ -94,6 +96,8 @@ struct CacheBackgroundResult {
9496
QSize area;
9597
int x = 0;
9698
int y = 0;
99+
QRect giftArea;
100+
int giftRotation = 0;
97101
bool waitingForNegativePattern = false;
98102
};
99103

@@ -108,6 +112,9 @@ struct CachedBackground {
108112
QSize area;
109113
int x = 0;
110114
int y = 0;
115+
QRect giftArea;
116+
int giftRotation = 0;
117+
mutable std::unique_ptr<Text::CustomEmoji> gift;
111118
bool waitingForNegativePattern = false;
112119
};
113120

Telegram/SourceFiles/window/section_widget.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,50 +325,102 @@ void SectionWidget::PaintBackground(
325325
not_null<Ui::ChatTheme*> theme,
326326
not_null<QWidget*> widget,
327327
QRect clip) {
328+
if (const auto id = theme->background().giftId) {
329+
const auto fillHeight = controller->content()->height();
330+
const auto fill = QSize(widget->width(), fillHeight);
331+
const auto &state = theme->backgroundState(fill);
332+
const auto make = [&] {
333+
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
334+
controller->session().data().customEmojiManager().create(
335+
id,
336+
crl::guard(widget, [=] { widget->update(); }),
337+
Data::CustomEmojiSizeTag::Isolated),
338+
1);
339+
};
340+
if (!state.was.gift) {
341+
state.was.gift = make();
342+
}
343+
if (!state.now.gift) {
344+
state.now.gift = make();
345+
}
346+
}
347+
328348
PaintBackground(
329349
theme,
330350
widget,
331351
controller->content()->height(),
332352
controller->content()->backgroundFromY(),
333-
clip);
353+
clip,
354+
controller->isGifPausedAtLeastFor(GifPauseReason::Any));
334355
}
335356

336357
void SectionWidget::PaintBackground(
337358
not_null<Ui::ChatTheme*> theme,
338359
not_null<QWidget*> widget,
339360
int fillHeight,
340361
int fromy,
341-
QRect clip) {
362+
QRect clip,
363+
bool paused) {
342364
auto p = QPainter(widget);
343365
if (fromy) {
344366
p.translate(0, fromy);
345367
clip = clip.translated(0, -fromy);
346368
}
347-
PaintBackground(p, theme, QSize(widget->width(), fillHeight), clip);
369+
const auto fill = QSize(widget->width(), fillHeight);
370+
PaintBackground(p, theme, fill, clip, paused);
348371
}
349372

350373
void SectionWidget::PaintBackground(
351374
QPainter &p,
352375
not_null<Ui::ChatTheme*> theme,
353376
QSize fill,
354-
QRect clip) {
377+
QRect clip,
378+
bool paused) {
355379
const auto &background = theme->background();
356380
if (background.colorForFill) {
357381
p.fillRect(clip, *background.colorForFill);
358382
return;
359383
}
360384
const auto &gradient = background.gradientForFill;
361-
auto state = theme->backgroundState(fill);
385+
const auto &state = theme->backgroundState(fill);
362386
const auto paintCache = [&](const Ui::CachedBackground &cache) {
363387
const auto to = QRect(
364388
QPoint(cache.x, cache.y),
365389
cache.pixmap.size() / style::DevicePixelRatio());
390+
const auto paintGift = [&](QRect area) {
391+
if (!cache.gift) {
392+
return;
393+
}
394+
auto hq = PainterHighQualityEnabler(p);
395+
const auto center = area.center();
396+
const auto size = Data::FrameSizeFromTag(
397+
Data::CustomEmojiSizeTag::Isolated
398+
) / style::DevicePixelRatio();
399+
p.save();
400+
p.translate(center);
401+
p.rotate(cache.giftRotation);
402+
p.translate(-center);
403+
p.setOpacity(0.5);
404+
cache.gift->paint(p, {
405+
.textColor = st::windowFg->c,
406+
.size = QSize(size, size),
407+
.now = crl::now(),
408+
.scale = (area.width() / float64(size)),
409+
.position = area.topLeft(),
410+
.paused = paused,
411+
.scaled = true,
412+
});
413+
p.restore();
414+
};
366415
if (cache.waitingForNegativePattern) {
367416
// While we wait for pattern being loaded we paint just gradient.
368417
// But in case of negative patter opacity we just fill-black.
369418
p.fillRect(to, Qt::black);
370419
} else if (cache.area == fill) {
371420
p.drawPixmap(to, cache.pixmap);
421+
if (background.giftId && !cache.giftArea.isEmpty()) {
422+
paintGift(cache.giftArea.translated(to.topLeft()));
423+
}
372424
} else {
373425
const auto sx = fill.width() / float64(cache.area.width());
374426
const auto sy = fill.height() / float64(cache.area.height());
@@ -384,6 +436,13 @@ void SectionWidget::PaintBackground(
384436
round((to.x() + to.width()) * sx) - sto.x(),
385437
round((to.y() + to.height()) * sy) - sto.y(),
386438
cache.pixmap);
439+
if (background.giftId && !cache.giftArea.isEmpty()) {
440+
paintGift(QRect(
441+
(to.x() + cache.giftArea.x()) * sx,
442+
(to.y() + cache.giftArea.y()) * sy,
443+
cache.giftArea.width() * sx,
444+
cache.giftArea.height() * sy));
445+
}
387446
}
388447
};
389448
const auto hasNow = !state.now.pixmap.isNull();

Telegram/SourceFiles/window/section_widget.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,14 @@ class SectionWidget : public AbstractSectionWidget {
207207
not_null<QWidget*> widget,
208208
int fillHeight,
209209
int fromy,
210-
QRect clip);
210+
QRect clip,
211+
bool paused = false);
211212
static void PaintBackground(
212213
QPainter &p,
213214
not_null<Ui::ChatTheme*> theme,
214215
QSize fill,
215-
QRect clip);
216+
QRect clip,
217+
bool paused = false);
216218

217219
protected:
218220
void paintEvent(QPaintEvent *e) override;

Telegram/SourceFiles/window/window_session_controller.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,7 @@ struct SessionController::CachedTheme {
15061506
std::weak_ptr<Ui::ChatTheme> theme;
15071507
std::shared_ptr<Data::DocumentMedia> media;
15081508
std::unique_ptr<Ui::Text::CustomEmoji> giftSymbol;
1509+
uint64 giftId = 0;
15091510
Data::WallPaper paper;
15101511
bool basedOnDark = false;
15111512
bool caching = false;
@@ -3302,13 +3303,17 @@ void SessionController::cacheChatTheme(
33023303
crl::guard(this, [=] { _giftSymbolLoaded.fire({}); }),
33033304
Data::CustomEmojiSizeTag::Large)
33043305
: nullptr;
3306+
const auto giftId = findGiftSymbols
3307+
? data.unique->model.document->id
3308+
: uint64();
33053309
const auto giftSymbolReady = !giftSymbol || giftSymbol->ready();
33063310
use.loadDocument();
33073311
auto &theme = [&]() -> CachedTheme& {
33083312
const auto i = _customChatThemes.find(key);
33093313
if (i != end(_customChatThemes)) {
33103314
i->second.media = media;
33113315
i->second.giftSymbol = std::move(giftSymbol);
3316+
i->second.giftId = giftId;
33123317
i->second.paper = use;
33133318
i->second.basedOnDark = dark;
33143319
i->second.caching = true;
@@ -3319,6 +3324,7 @@ void SessionController::cacheChatTheme(
33193324
CachedTheme{
33203325
.media = media,
33213326
.giftSymbol = std::move(giftSymbol),
3327+
.giftId = giftId,
33223328
.paper = use,
33233329
.basedOnDark = dark,
33243330
.caching = true,
@@ -3451,6 +3457,7 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
34513457
.path = paperPath,
34523458
.bytes = paperBytes,
34533459
.giftSymbolFrame = Ui::PrepareGiftSymbol(theme.giftSymbol),
3460+
.giftId = theme.giftId,
34543461
.gzipSvg = gzipSvg,
34553462
.colors = colors,
34563463
.isPattern = isPattern,

0 commit comments

Comments
 (0)