Extract history/view/media/history_view_media_generic module.

This commit is contained in:
John Preston 2024-03-21 14:35:58 +04:00
parent b31c30b9b6
commit 1e1a48865f
7 changed files with 1092 additions and 1051 deletions

View File

@ -718,6 +718,8 @@ PRIVATE
history/view/media/history_view_media.h
history/view/media/history_view_media_common.cpp
history/view/media/history_view_media_common.h
history/view/media/history_view_media_generic.cpp
history/view/media/history_view_media_generic.h
history/view/media/history_view_media_grouped.cpp
history/view/media/history_view_media_grouped.h
history/view/media/history_view_media_spoiler.cpp

View File

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_game.h"
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_invoice.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_call.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_poll.h"
@ -2321,7 +2322,7 @@ std::unique_ptr<HistoryView::Media> MediaGiveawayStart::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::MediaInBubble>(
return std::make_unique<HistoryView::MediaGeneric>(
message,
HistoryView::GenerateGiveawayStart(message, &_data));
}
@ -2370,7 +2371,7 @@ std::unique_ptr<HistoryView::Media> MediaGiveawayResults::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::MediaInBubble>(
return std::make_unique<HistoryView::MediaGeneric>(
message,
HistoryView::GenerateGiveawayResults(message, &_data));
}

View File

@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_service_box.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "history/view/media/history_view_sticker.h"
@ -79,8 +79,8 @@ auto GenerateChatIntro(
Element *replacing,
const Data::ChatIntro &data,
Fn<void(not_null<DocumentData*>)> helloChosen)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)> {
return [=](Fn<void(std::unique_ptr<MediaInBubble::Part>)> push) {
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
auto pushText = [&](
TextWithEntities text,
QMargins margins = {},
@ -88,7 +88,7 @@ auto GenerateChatIntro(
if (text.empty()) {
return;
}
push(std::make_unique<TextMediaInBubblePart>(
push(std::make_unique<MediaGenericTextPart>(
std::move(text),
margins,
links));
@ -287,10 +287,10 @@ void AboutView::make(Data::ChatIntro data) {
const auto helloChosen = [=](not_null<DocumentData*> sticker) {
setHelloChosen(sticker);
};
owned->overrideMedia(std::make_unique<HistoryView::MediaInBubble>(
owned->overrideMedia(std::make_unique<HistoryView::MediaGeneric>(
owned.get(),
GenerateChatIntro(owned.get(), _item.get(), data, helloChosen),
HistoryView::MediaInBubbleDescriptor{
HistoryView::MediaGenericDescriptor{
.maxWidth = st::chatIntroWidth,
.service = true,
.hideServiceText = true,

View File

@ -13,765 +13,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_dice_pack.h"
#include "countries/countries_instance.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/message_bubble.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/tooltip.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
constexpr auto kAdditionalPrizesWithLineOpacity = 0.6;
[[nodiscard]] QSize CountOptimalTextSize(
const Ui::Text::String &text,
int minWidth,
int maxWidth) {
if (text.maxWidth() <= maxWidth) {
return { text.maxWidth(), text.minHeight() };
}
const auto height = text.countHeight(maxWidth);
return { Ui::FindNiceTooltipWidth(minWidth, maxWidth, [&](int width) {
return text.countHeight(width);
}), height };
}
} // namespace
TextState MediaInBubble::Part::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
return {};
}
void MediaInBubble::Part::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
}
bool MediaInBubble::Part::hasHeavyPart() {
return false;
}
void MediaInBubble::Part::unloadHeavyPart() {
}
auto MediaInBubble::Part::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements
) -> std::unique_ptr<StickerPlayer> {
return nullptr;
}
MediaInBubble::MediaInBubble(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
MediaInBubbleDescriptor &&descriptor)
: Media(parent)
, _maxWidthCap(descriptor.maxWidth)
, _service(descriptor.service)
, _hideServiceText(descriptor.hideServiceText) {
generate([&](std::unique_ptr<Part> part) {
_entries.push_back({
.object = std::move(part),
});
});
}
MediaInBubble::~MediaInBubble() {
if (hasHeavyPart()) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
QSize MediaInBubble::countOptimalSize() {
const auto maxWidth = _maxWidthCap
? _maxWidthCap
: st::chatGiveawayWidth;
auto top = 0;
for (auto &entry : _entries) {
const auto raw = entry.object.get();
raw->initDimensions();
top += raw->resizeGetHeight(maxWidth);
}
return { maxWidth, top };
}
QSize MediaInBubble::countCurrentSize(int newWidth) {
return { maxWidth(), minHeight() };
}
void MediaInBubble::draw(Painter &p, const PaintContext &context) const {
const auto outer = width();
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return;
} else if (_service) {
PainterHighQualityEnabler hq(p);
const auto radius = st::msgServiceGiftBoxRadius;
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.drawRoundedRect(QRect(0, 0, width(), height()), radius, radius);
}
auto translated = 0;
for (const auto &entry : _entries) {
const auto raw = entry.object.get();
const auto height = raw->height();
raw->draw(p, this, context, outer);
translated += height;
p.translate(0, height);
}
p.translate(0, -translated);
}
TextState MediaInBubble::textState(
QPoint point,
StateRequest request) const {
auto result = TextState(_parent);
const auto outer = width();
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
for (const auto &entry : _entries) {
const auto raw = entry.object.get();
const auto height = raw->height();
if (point.y() >= 0 && point.y() < height) {
const auto part = raw->textState(point, request, outer);
result.link = part.link;
return result;
}
point.setY(point.y() - height);
}
return result;
}
void MediaInBubble::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
}
void MediaInBubble::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (const auto &entry : _entries) {
entry.object->clickHandlerPressedChanged(p, pressed);
}
}
std::unique_ptr<StickerPlayer> MediaInBubble::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
for (const auto &entry : _entries) {
if (auto result = entry.object->stickerTakePlayer(
data,
replacements)) {
return result;
}
}
return nullptr;
}
bool MediaInBubble::hideFromName() const {
return !parent()->data()->Has<HistoryMessageForwarded>();
}
bool MediaInBubble::hideServiceText() const {
return _hideServiceText;
}
bool MediaInBubble::hasHeavyPart() const {
for (const auto &entry : _entries) {
if (entry.object->hasHeavyPart()) {
return true;
}
}
return false;
}
void MediaInBubble::unloadHeavyPart() {
for (const auto &entry : _entries) {
entry.object->unloadHeavyPart();
}
}
QMargins MediaInBubble::inBubblePadding() const {
auto lshift = st::msgPadding.left();
auto rshift = st::msgPadding.right();
auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip;
auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip;
return QMargins(lshift, tshift, rshift, bshift);
}
TextMediaInBubblePart::TextMediaInBubblePart(
TextWithEntities text,
QMargins margins,
const base::flat_map<uint16, ClickHandlerPtr> &links)
: _text(st::msgMinWidth)
, _margins(margins) {
_text.setMarkedText(st::defaultTextStyle, text);
for (const auto &[index, link] : links) {
_text.setLink(index, link);
}
}
void TextMediaInBubblePart::draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const {
const auto service = owner->service();
p.setPen(service
? context.st->msgServiceFg()
: context.messageStyle()->historyTextFg);
_text.draw(p, {
.position = { (outerWidth - width()) / 2, _margins.top() },
.outerWidth = outerWidth,
.availableWidth = width(),
.align = style::al_top,
.palette = &(service
? context.st->serviceTextPalette()
: context.messageStyle()->textPalette),
.now = context.now,
});
}
TextState TextMediaInBubblePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
point -= QPoint{ (outerWidth - width()) / 2, _margins.top() };
auto result = TextState();
auto forText = request.forText();
forText.align = style::al_top;
result.link = _text.getState(point, width(), forText).link;
return result;
}
QSize TextMediaInBubblePart::countOptimalSize() {
return {
_margins.left() + _text.maxWidth() + _margins.right(),
_margins.top() + _text.minHeight() + _margins.bottom(),
};
}
QSize TextMediaInBubblePart::countCurrentSize(int newWidth) {
auto skip = _margins.left() + _margins.right();
const auto size = CountOptimalTextSize(
_text,
st::msgMinWidth,
newWidth - skip);
return {
size.width() + skip,
_margins.top() + size.height() + _margins.bottom(),
};
}
TextDelimeterPart::TextDelimeterPart(
const QString &text,
QMargins margins)
: _margins(margins) {
_text.setText(st::defaultTextStyle, text);
}
void TextDelimeterPart::draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const {
const auto stm = context.messageStyle();
const auto available = outerWidth - _margins.left() - _margins.right();
p.setPen(stm->msgDateFg);
_text.draw(p, {
.position = { _margins.left(), _margins.top() },
.outerWidth = outerWidth,
.availableWidth = available,
.align = style::al_top,
.palette = &stm->textPalette,
.now = context.now,
.elisionLines = 1,
});
const auto skip = st::chatGiveawayPrizesWithSkip;
const auto inner = available - 2 * skip;
const auto sub = _text.maxWidth();
if (inner > sub + 1) {
const auto fill = (inner - sub) / 2;
const auto stroke = st::lineWidth;
const auto top = _margins.top()
+ st::chatGiveawayPrizesWithLineTop;
p.setOpacity(kAdditionalPrizesWithLineOpacity);
p.fillRect(_margins.left(), top, fill, stroke, stm->msgDateFg);
const auto start = outerWidth - _margins.right() - fill;
p.fillRect(start, top, fill, stroke, stm->msgDateFg);
p.setOpacity(1.);
}
}
QSize TextDelimeterPart::countOptimalSize() {
return {
_margins.left() + _text.maxWidth() + _margins.right(),
_margins.top() + st::normalFont->height + _margins.bottom(),
};
}
QSize TextDelimeterPart::countCurrentSize(int newWidth) {
return { newWidth, minHeight() };
}
StickerInBubblePart::StickerInBubblePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding)
: _parent(parent)
, _lookup(std::move(lookup))
, _padding(padding) {
ensureCreated(replacing);
}
void StickerInBubblePart::draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const {
ensureCreated();
if (_sticker) {
const auto stickerSize = _sticker->countOptimalSize();
const auto sticker = QRect(
(outerWidth - stickerSize.width()) / 2,
_padding.top() + _skipTop,
stickerSize.width(),
stickerSize.height());
_sticker->draw(p, context, sticker);
}
}
TextState StickerInBubblePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
auto result = TextState(_parent);
if (_sticker) {
const auto stickerSize = _sticker->countOptimalSize();
const auto sticker = QRect(
(outerWidth - stickerSize.width()) / 2,
_padding.top() + _skipTop,
stickerSize.width(),
stickerSize.height());
if (sticker.contains(point)) {
result.link = _link;
}
}
return result;
}
bool StickerInBubblePart::hasHeavyPart() {
return _sticker && _sticker->hasHeavyPart();
}
void StickerInBubblePart::unloadHeavyPart() {
if (_sticker) {
_sticker->unloadHeavyPart();
}
}
std::unique_ptr<StickerPlayer> StickerInBubblePart::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}
QSize StickerInBubblePart::countOptimalSize() {
ensureCreated();
const auto size = _sticker ? _sticker->countOptimalSize() : [&] {
const auto fallback = _lookup().size;
return QSize{ fallback, fallback };
}();
return {
_padding.left() + size.width() + _padding.right(),
_padding.top() + size.height() + _padding.bottom(),
};
}
QSize StickerInBubblePart::countCurrentSize(int newWidth) {
return { newWidth, minHeight() };
}
void StickerInBubblePart::ensureCreated(Element *replacing) const {
if (_sticker) {
return;
} else if (const auto data = _lookup()) {
const auto sticker = data.sticker;
if (const auto info = sticker->sticker()) {
const auto skipPremiumEffect = true;
_link = data.link;
_skipTop = data.skipTop;
_sticker.emplace(_parent, sticker, skipPremiumEffect, replacing);
if (data.singleTimePlayback) {
_sticker->setDiceIndex(info->alt, 1);
}
_sticker->initSize(data.size);
_sticker->setCustomCachingTag(data.cacheTag);
}
}
}
StickerWithBadgePart::StickerWithBadgePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding,
QString badge)
: _sticker(parent, replacing, std::move(lookup), padding)
, _badgeText(badge) {
}
void StickerWithBadgePart::draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const {
_sticker.draw(p, owner, context, outerWidth);
if (_sticker.resolved()) {
paintBadge(p, context);
}
}
TextState StickerWithBadgePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
return _sticker.textState(point, request, outerWidth);
}
bool StickerWithBadgePart::hasHeavyPart() {
return _sticker.hasHeavyPart();
}
void StickerWithBadgePart::unloadHeavyPart() {
_sticker.unloadHeavyPart();
}
std::unique_ptr<StickerPlayer> StickerWithBadgePart::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker.stickerTakePlayer(data, replacements);
}
QSize StickerWithBadgePart::countOptimalSize() {
_sticker.initDimensions();
return { _sticker.maxWidth(), _sticker.minHeight() };
}
QSize StickerWithBadgePart::countCurrentSize(int newWidth) {
return _sticker.countCurrentSize(newWidth);
}
void StickerWithBadgePart::paintBadge(
Painter &p,
const PaintContext &context) const {
validateBadge(context);
const auto badge = _badge.size() / _badge.devicePixelRatio();
const auto left = (width() - badge.width()) / 2;
const auto top = st::chatGiveawayBadgeTop;
const auto rect = QRect(left, top, badge.width(), badge.height());
const auto paintContent = [&](QPainter &q) {
q.drawImage(rect.topLeft(), _badge);
};
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(context.messageStyle()->msgFileBg);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto inner = QRectF(rect).marginsRemoved(
{ half, half, half, half });
const auto radius = inner.height() / 2.;
p.drawRoundedRect(inner, radius, radius);
}
if (!_sticker.parent()->usesBubblePattern(context)) {
paintContent(p);
} else {
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
rect,
paintContent,
_badgeCache);
}
}
void StickerWithBadgePart::validateBadge(
const PaintContext &context) const {
const auto stm = context.messageStyle();
const auto &badgeFg = stm->historyFileRadialFg->c;
const auto &badgeBorder = stm->msgBg->c;
if (!_badge.isNull()
&& _badgeFg == badgeFg
&& _badgeBorder == badgeBorder) {
return;
}
const auto &font = st::chatGiveawayBadgeFont;
_badgeFg = badgeFg;
_badgeBorder = badgeBorder;
const auto width = font->width(_badgeText);
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding);
const auto size = rect.size();
const auto ratio = style::DevicePixelRatio();
_badge = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
_badge.setDevicePixelRatio(ratio);
_badge.fill(Qt::transparent);
auto p = QPainter(&_badge);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(_badgeBorder, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(Qt::NoBrush);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto smaller = QRectF(
rect.translated(-rect.topLeft())
).marginsRemoved({ half, half, half, half });
const auto radius = smaller.height() / 2.;
p.drawRoundedRect(smaller, radius, radius);
p.setPen(_badgeFg);
p.setFont(font);
p.drawText(
st::chatGiveawayBadgePadding.left(),
st::chatGiveawayBadgePadding.top() + font->ascent,
_badgeText);
}
PeerBubbleListPart::PeerBubbleListPart(
not_null<Element*> parent,
const std::vector<not_null<PeerData*>> &list)
: _parent(parent) {
for (const auto &peer : list) {
_peers.push_back({
.name = Ui::Text::String(
st::semiboldTextStyle,
peer->name(),
kDefaultTextOptions,
st::msgMinWidth),
.thumbnail = Ui::MakeUserpicThumbnail(peer),
.link = peer->openLink(),
.colorIndex = peer->colorIndex(),
});
}
}
PeerBubbleListPart::~PeerBubbleListPart() = default;
void PeerBubbleListPart::draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const {
if (_peers.empty()) {
return;
}
const auto size = _peers[0].geometry.height();
const auto st = context.st;
const auto stm = context.messageStyle();
const auto selected = context.selected();
const auto padding = st::chatGiveawayPeerPadding;
for (const auto &peer : _peers) {
const auto &thumbnail = peer.thumbnail;
const auto &geometry = peer.geometry;
if (!_subscribed) {
thumbnail->subscribeToUpdates([=] { _parent->repaint(); });
}
const auto colorIndex = peer.colorIndex;
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get();
if (peer.corners[0].isNull() || peer.bg != cache->bg) {
peer.bg = cache->bg;
peer.corners = Images::CornersMask(size / 2);
for (auto &image : peer.corners) {
style::colorizeImage(image, cache->bg, &image);
}
}
p.setPen(cache->icon);
Ui::DrawRoundedRect(p, geometry, peer.bg, peer.corners);
if (peer.ripple) {
peer.ripple->paint(
p,
geometry.x(),
geometry.y(),
width(),
&cache->bg);
if (peer.ripple->empty()) {
peer.ripple = nullptr;
}
}
p.drawImage(geometry.topLeft(), thumbnail->image(size));
const auto left = size + padding.left();
const auto top = padding.top();
const auto available = geometry.width() - left - padding.right();
peer.name.draw(p, {
.position = { geometry.left() + left, geometry.top() + top },
.outerWidth = width(),
.availableWidth = available,
.align = style::al_left,
.palette = &stm->textPalette,
.now = context.now,
.elisionLines = 1,
.elisionBreakEverywhere = true,
});
}
_subscribed = true;
}
int PeerBubbleListPart::layout(int x, int y, int available) {
const auto size = st::chatGiveawayPeerSize;
const auto skip = st::chatGiveawayPeerSkip;
const auto padding = st::chatGiveawayPeerPadding;
auto left = available;
const auto shiftRow = [&](int i, int top, int shift) {
for (auto j = i; j != 0; --j) {
auto &geometry = _peers[j - 1].geometry;
if (geometry.top() != top) {
break;
}
geometry.moveLeft(geometry.x() + shift);
}
};
const auto count = int(_peers.size());
for (auto i = 0; i != count; ++i) {
const auto desired = size
+ padding.left()
+ _peers[i].name.maxWidth()
+ padding.right();
const auto width = std::min(desired, available);
if (left < width) {
shiftRow(i, y, (left + skip) / 2);
left = available;
y += size + skip;
}
_peers[i].geometry = { x + available - left, y, width, size };
left -= width + skip;
}
shiftRow(count, y, (left + skip) / 2);
return y + size + skip;
}
TextState PeerBubbleListPart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
auto result = TextState(_parent);
for (const auto &peer : _peers) {
if (peer.geometry.contains(point)) {
result.link = peer.link;
_lastPoint = point;
break;
}
}
return result;
}
void PeerBubbleListPart::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (auto &peer : _peers) {
if (peer.link != p) {
continue;
}
if (pressed) {
if (!peer.ripple) {
peer.ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
peer.geometry.size(),
peer.geometry.height() / 2),
[=] { _parent->repaint(); });
}
peer.ripple->add(_lastPoint - peer.geometry.topLeft());
} else if (peer.ripple) {
peer.ripple->lastStop();
}
break;
}
}
bool PeerBubbleListPart::hasHeavyPart() {
return _subscribed;
}
void PeerBubbleListPart::unloadHeavyPart() {
if (_subscribed) {
_subscribed = false;
for (const auto &peer : _peers) {
peer.thumbnail->subscribeToUpdates(nullptr);
}
}
}
QSize PeerBubbleListPart::countOptimalSize() {
if (_peers.empty()) {
return {};
}
const auto size = st::chatGiveawayPeerSize;
const auto skip = st::chatGiveawayPeerSkip;
const auto padding = st::chatGiveawayPeerPadding;
auto left = st::msgPadding.left();
for (const auto &peer : _peers) {
const auto desired = size
+ padding.left()
+ peer.name.maxWidth()
+ padding.right();
left += desired + skip;
}
return { left - skip + st::msgPadding.right(), size };
}
QSize PeerBubbleListPart::countCurrentSize(int newWidth) {
if (_peers.empty()) {
return {};
}
const auto padding = st::msgPadding;
const auto available = newWidth - padding.left() - padding.right();
const auto channelsBottom = layout(
padding.left(),
0,
available);
return { newWidth, channelsBottom };
}
auto GenerateGiveawayStart(
not_null<Element*> parent,
not_null<Data::GiveawayStart*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)> {
return [=](Fn<void(std::unique_ptr<MediaInBubble::Part>)> push) {
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto months = data->months;
const auto quantity = data->quantity;
@ -799,7 +58,7 @@ auto GenerateGiveawayStart(
TextWithEntities text,
QMargins margins = {},
const base::flat_map<uint16, ClickHandlerPtr> &links = {}) {
push(std::make_unique<TextMediaInBubblePart>(
push(std::make_unique<MediaGenericTextPart>(
std::move(text),
margins,
links));
@ -907,8 +166,8 @@ auto GenerateGiveawayStart(
auto GenerateGiveawayResults(
not_null<Element*> parent,
not_null<Data::GiveawayResults*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)> {
return [=](Fn<void(std::unique_ptr<MediaInBubble::Part>)> push) {
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto quantity = data->winnersCount;
using Data = StickerWithBadgePart::Data;
@ -936,7 +195,7 @@ auto GenerateGiveawayResults(
TextWithEntities text,
QMargins margins = {},
const base::flat_map<uint16, ClickHandlerPtr> &links = {}) {
push(std::make_unique<TextMediaInBubblePart>(
push(std::make_unique<MediaGenericTextPart>(
std::move(text),
margins,
links));

View File

@ -7,314 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_sticker.h"
namespace Data {
struct GiveawayStart;
struct GiveawayResults;
} // namespace Data
namespace Ui {
class DynamicImage;
class RippleAnimation;
} // namespace Ui
namespace HistoryView {
struct MediaInBubbleDescriptor {
int maxWidth = 0;
bool service = false;
bool hideServiceText = false;
};
class MediaInBubble final : public Media {
public:
class Part : public Object {
public:
virtual ~Part() = default;
virtual void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const = 0;
[[nodiscard]] virtual TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const;
virtual void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed);
[[nodiscard]] virtual bool hasHeavyPart();
virtual void unloadHeavyPart();
[[nodiscard]] virtual auto stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements
) -> std::unique_ptr<StickerPlayer>;
};
MediaInBubble(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
MediaInBubbleDescriptor &&descriptor = {});
~MediaInBubble();
[[nodiscard]] bool service() const {
return _service;
}
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
void clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
bool needsBubble() const override {
return !_service;
}
bool customInfoLayout() const override {
return false;
}
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override {
return true;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return true;
}
bool hideFromName() const override;
bool hideServiceText() const override;
void unloadHeavyPart() override;
bool hasHeavyPart() const override;
private:
struct Entry {
std::unique_ptr<Part> object;
};
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
[[nodiscard]] QMargins inBubblePadding() const;
std::vector<Entry> _entries;
int _maxWidthCap = 0;
bool _service : 1 = false;
bool _hideServiceText : 1 = false;
};
class TextMediaInBubblePart final : public MediaInBubble::Part {
public:
TextMediaInBubblePart(
TextWithEntities text,
QMargins margins,
const base::flat_map<uint16, ClickHandlerPtr> &links = {});
void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class TextDelimeterPart final : public MediaInBubble::Part {
public:
TextDelimeterPart(const QString &text, QMargins margins);
void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class StickerInBubblePart final : public MediaInBubble::Part {
public:
struct Data {
DocumentData *sticker = nullptr;
int skipTop = 0;
int size = 0;
ChatHelpers::StickerLottieSize cacheTag = {};
bool singleTimePlayback = false;
ClickHandlerPtr link;
explicit operator bool() const {
return sticker != nullptr;
}
};
StickerInBubblePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding);
[[nodiscard]] not_null<Element*> parent() const {
return _parent;
}
[[nodiscard]] bool resolved() const {
return _sticker.has_value();
}
void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
private:
void ensureCreated(Element *replacing = nullptr) const;
const not_null<Element*> _parent;
Fn<Data()> _lookup;
mutable int _skipTop = 0;
mutable QMargins _padding;
mutable std::optional<Sticker> _sticker;
mutable ClickHandlerPtr _link;
};
class StickerWithBadgePart final : public MediaInBubble::Part {
public:
using Data = StickerInBubblePart::Data;
StickerWithBadgePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding,
QString badge);
void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
private:
void validateBadge(const PaintContext &context) const;
void paintBadge(Painter &p, const PaintContext &context) const;
StickerInBubblePart _sticker;
QString _badgeText;
mutable QColor _badgeFg;
mutable QColor _badgeBorder;
mutable QImage _badge;
mutable QImage _badgeCache;
};
class PeerBubbleListPart final : public MediaInBubble::Part {
public:
PeerBubbleListPart(
not_null<Element*> parent,
const std::vector<not_null<PeerData*>> &list);
~PeerBubbleListPart();
void draw(
Painter &p,
not_null<const MediaInBubble*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
int layout(int x, int y, int available);
struct Peer {
Ui::Text::String name;
std::shared_ptr<Ui::DynamicImage> thumbnail;
QRect geometry;
ClickHandlerPtr link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
mutable std::array<QImage, 4> corners;
mutable QColor bg;
uint8 colorIndex = 0;
};
const not_null<Element*> _parent;
std::vector<Peer> _peers;
mutable QPoint _lastPoint;
mutable bool _subscribed = false;
};
class Element;
class MediaGenericPart;
[[nodiscard]] auto GenerateGiveawayStart(
not_null<Element*> parent,
not_null<Data::GiveawayStart*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] auto GenerateGiveawayResults(
not_null<Element*> parent,
not_null<Data::GiveawayResults*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
} // namespace HistoryView

View File

@ -0,0 +1,760 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_media_generic.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/tooltip.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
constexpr auto kAdditionalPrizesWithLineOpacity = 0.6;
[[nodiscard]] QSize CountOptimalTextSize(
const Ui::Text::String &text,
int minWidth,
int maxWidth) {
if (text.maxWidth() <= maxWidth) {
return { text.maxWidth(), text.minHeight() };
}
const auto height = text.countHeight(maxWidth);
return { Ui::FindNiceTooltipWidth(minWidth, maxWidth, [&](int width) {
return text.countHeight(width);
}), height };
}
} // namespace
TextState MediaGenericPart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
return {};
}
void MediaGenericPart::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
}
bool MediaGenericPart::hasHeavyPart() {
return false;
}
void MediaGenericPart::unloadHeavyPart() {
}
auto MediaGenericPart::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements
) -> std::unique_ptr<StickerPlayer> {
return nullptr;
}
MediaGeneric::MediaGeneric(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
MediaGenericDescriptor &&descriptor)
: Media(parent)
, _maxWidthCap(descriptor.maxWidth)
, _service(descriptor.service)
, _hideServiceText(descriptor.hideServiceText) {
generate([&](std::unique_ptr<Part> part) {
_entries.push_back({
.object = std::move(part),
});
});
}
MediaGeneric::~MediaGeneric() {
if (hasHeavyPart()) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
QSize MediaGeneric::countOptimalSize() {
const auto maxWidth = _maxWidthCap
? _maxWidthCap
: st::chatGiveawayWidth;
auto top = 0;
for (auto &entry : _entries) {
const auto raw = entry.object.get();
raw->initDimensions();
top += raw->resizeGetHeight(maxWidth);
}
return { maxWidth, top };
}
QSize MediaGeneric::countCurrentSize(int newWidth) {
return { maxWidth(), minHeight() };
}
void MediaGeneric::draw(Painter &p, const PaintContext &context) const {
const auto outer = width();
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return;
} else if (_service) {
PainterHighQualityEnabler hq(p);
const auto radius = st::msgServiceGiftBoxRadius;
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.drawRoundedRect(QRect(0, 0, width(), height()), radius, radius);
}
auto translated = 0;
for (const auto &entry : _entries) {
const auto raw = entry.object.get();
const auto height = raw->height();
raw->draw(p, this, context, outer);
translated += height;
p.translate(0, height);
}
p.translate(0, -translated);
}
TextState MediaGeneric::textState(
QPoint point,
StateRequest request) const {
auto result = TextState(_parent);
const auto outer = width();
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
for (const auto &entry : _entries) {
const auto raw = entry.object.get();
const auto height = raw->height();
if (point.y() >= 0 && point.y() < height) {
const auto part = raw->textState(point, request, outer);
result.link = part.link;
return result;
}
point.setY(point.y() - height);
}
return result;
}
void MediaGeneric::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
}
void MediaGeneric::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (const auto &entry : _entries) {
entry.object->clickHandlerPressedChanged(p, pressed);
}
}
std::unique_ptr<StickerPlayer> MediaGeneric::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
for (const auto &entry : _entries) {
if (auto result = entry.object->stickerTakePlayer(
data,
replacements)) {
return result;
}
}
return nullptr;
}
bool MediaGeneric::hideFromName() const {
return !parent()->data()->Has<HistoryMessageForwarded>();
}
bool MediaGeneric::hideServiceText() const {
return _hideServiceText;
}
bool MediaGeneric::hasHeavyPart() const {
for (const auto &entry : _entries) {
if (entry.object->hasHeavyPart()) {
return true;
}
}
return false;
}
void MediaGeneric::unloadHeavyPart() {
for (const auto &entry : _entries) {
entry.object->unloadHeavyPart();
}
}
QMargins MediaGeneric::inBubblePadding() const {
auto lshift = st::msgPadding.left();
auto rshift = st::msgPadding.right();
auto bshift = isBubbleBottom()
? st::msgPadding.top()
: st::mediaInBubbleSkip;
auto tshift = isBubbleTop()
? st::msgPadding.bottom()
: st::mediaInBubbleSkip;
return QMargins(lshift, tshift, rshift, bshift);
}
MediaGenericTextPart::MediaGenericTextPart(
TextWithEntities text,
QMargins margins,
const base::flat_map<uint16, ClickHandlerPtr> &links)
: _text(st::msgMinWidth)
, _margins(margins) {
_text.setMarkedText(st::defaultTextStyle, text);
for (const auto &[index, link] : links) {
_text.setLink(index, link);
}
}
void MediaGenericTextPart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
const auto service = owner->service();
p.setPen(service
? context.st->msgServiceFg()
: context.messageStyle()->historyTextFg);
_text.draw(p, {
.position = { (outerWidth - width()) / 2, _margins.top() },
.outerWidth = outerWidth,
.availableWidth = width(),
.align = style::al_top,
.palette = &(service
? context.st->serviceTextPalette()
: context.messageStyle()->textPalette),
.now = context.now,
});
}
TextState MediaGenericTextPart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
point -= QPoint{ (outerWidth - width()) / 2, _margins.top() };
auto result = TextState();
auto forText = request.forText();
forText.align = style::al_top;
result.link = _text.getState(point, width(), forText).link;
return result;
}
QSize MediaGenericTextPart::countOptimalSize() {
return {
_margins.left() + _text.maxWidth() + _margins.right(),
_margins.top() + _text.minHeight() + _margins.bottom(),
};
}
QSize MediaGenericTextPart::countCurrentSize(int newWidth) {
auto skip = _margins.left() + _margins.right();
const auto size = CountOptimalTextSize(
_text,
st::msgMinWidth,
newWidth - skip);
return {
size.width() + skip,
_margins.top() + size.height() + _margins.bottom(),
};
}
TextDelimeterPart::TextDelimeterPart(
const QString &text,
QMargins margins)
: _margins(margins) {
_text.setText(st::defaultTextStyle, text);
}
void TextDelimeterPart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
const auto stm = context.messageStyle();
const auto available = outerWidth - _margins.left() - _margins.right();
p.setPen(stm->msgDateFg);
_text.draw(p, {
.position = { _margins.left(), _margins.top() },
.outerWidth = outerWidth,
.availableWidth = available,
.align = style::al_top,
.palette = &stm->textPalette,
.now = context.now,
.elisionLines = 1,
});
const auto skip = st::chatGiveawayPrizesWithSkip;
const auto inner = available - 2 * skip;
const auto sub = _text.maxWidth();
if (inner > sub + 1) {
const auto fill = (inner - sub) / 2;
const auto stroke = st::lineWidth;
const auto top = _margins.top()
+ st::chatGiveawayPrizesWithLineTop;
p.setOpacity(kAdditionalPrizesWithLineOpacity);
p.fillRect(_margins.left(), top, fill, stroke, stm->msgDateFg);
const auto start = outerWidth - _margins.right() - fill;
p.fillRect(start, top, fill, stroke, stm->msgDateFg);
p.setOpacity(1.);
}
}
QSize TextDelimeterPart::countOptimalSize() {
return {
_margins.left() + _text.maxWidth() + _margins.right(),
_margins.top() + st::normalFont->height + _margins.bottom(),
};
}
QSize TextDelimeterPart::countCurrentSize(int newWidth) {
return { newWidth, minHeight() };
}
StickerInBubblePart::StickerInBubblePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding)
: _parent(parent)
, _lookup(std::move(lookup))
, _padding(padding) {
ensureCreated(replacing);
}
void StickerInBubblePart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
ensureCreated();
if (_sticker) {
const auto stickerSize = _sticker->countOptimalSize();
const auto sticker = QRect(
(outerWidth - stickerSize.width()) / 2,
_padding.top() + _skipTop,
stickerSize.width(),
stickerSize.height());
_sticker->draw(p, context, sticker);
}
}
TextState StickerInBubblePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
auto result = TextState(_parent);
if (_sticker) {
const auto stickerSize = _sticker->countOptimalSize();
const auto sticker = QRect(
(outerWidth - stickerSize.width()) / 2,
_padding.top() + _skipTop,
stickerSize.width(),
stickerSize.height());
if (sticker.contains(point)) {
result.link = _link;
}
}
return result;
}
bool StickerInBubblePart::hasHeavyPart() {
return _sticker && _sticker->hasHeavyPart();
}
void StickerInBubblePart::unloadHeavyPart() {
if (_sticker) {
_sticker->unloadHeavyPart();
}
}
std::unique_ptr<StickerPlayer> StickerInBubblePart::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}
QSize StickerInBubblePart::countOptimalSize() {
ensureCreated();
const auto size = _sticker ? _sticker->countOptimalSize() : [&] {
const auto fallback = _lookup().size;
return QSize{ fallback, fallback };
}();
return {
_padding.left() + size.width() + _padding.right(),
_padding.top() + size.height() + _padding.bottom(),
};
}
QSize StickerInBubblePart::countCurrentSize(int newWidth) {
return { newWidth, minHeight() };
}
void StickerInBubblePart::ensureCreated(Element *replacing) const {
if (_sticker) {
return;
} else if (const auto data = _lookup()) {
const auto sticker = data.sticker;
if (const auto info = sticker->sticker()) {
const auto skipPremiumEffect = true;
_link = data.link;
_skipTop = data.skipTop;
_sticker.emplace(_parent, sticker, skipPremiumEffect, replacing);
if (data.singleTimePlayback) {
_sticker->setDiceIndex(info->alt, 1);
}
_sticker->initSize(data.size);
_sticker->setCustomCachingTag(data.cacheTag);
}
}
}
StickerWithBadgePart::StickerWithBadgePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding,
QString badge)
: _sticker(parent, replacing, std::move(lookup), padding)
, _badgeText(badge) {
}
void StickerWithBadgePart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
_sticker.draw(p, owner, context, outerWidth);
if (_sticker.resolved()) {
paintBadge(p, context);
}
}
TextState StickerWithBadgePart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
return _sticker.textState(point, request, outerWidth);
}
bool StickerWithBadgePart::hasHeavyPart() {
return _sticker.hasHeavyPart();
}
void StickerWithBadgePart::unloadHeavyPart() {
_sticker.unloadHeavyPart();
}
std::unique_ptr<StickerPlayer> StickerWithBadgePart::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker.stickerTakePlayer(data, replacements);
}
QSize StickerWithBadgePart::countOptimalSize() {
_sticker.initDimensions();
return { _sticker.maxWidth(), _sticker.minHeight() };
}
QSize StickerWithBadgePart::countCurrentSize(int newWidth) {
return _sticker.countCurrentSize(newWidth);
}
void StickerWithBadgePart::paintBadge(
Painter &p,
const PaintContext &context) const {
validateBadge(context);
const auto badge = _badge.size() / _badge.devicePixelRatio();
const auto left = (width() - badge.width()) / 2;
const auto top = st::chatGiveawayBadgeTop;
const auto rect = QRect(left, top, badge.width(), badge.height());
const auto paintContent = [&](QPainter &q) {
q.drawImage(rect.topLeft(), _badge);
};
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(context.messageStyle()->msgFileBg);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto inner = QRectF(rect).marginsRemoved(
{ half, half, half, half });
const auto radius = inner.height() / 2.;
p.drawRoundedRect(inner, radius, radius);
}
if (!_sticker.parent()->usesBubblePattern(context)) {
paintContent(p);
} else {
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
rect,
paintContent,
_badgeCache);
}
}
void StickerWithBadgePart::validateBadge(
const PaintContext &context) const {
const auto stm = context.messageStyle();
const auto &badgeFg = stm->historyFileRadialFg->c;
const auto &badgeBorder = stm->msgBg->c;
if (!_badge.isNull()
&& _badgeFg == badgeFg
&& _badgeBorder == badgeBorder) {
return;
}
const auto &font = st::chatGiveawayBadgeFont;
_badgeFg = badgeFg;
_badgeBorder = badgeBorder;
const auto width = font->width(_badgeText);
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding);
const auto size = rect.size();
const auto ratio = style::DevicePixelRatio();
_badge = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
_badge.setDevicePixelRatio(ratio);
_badge.fill(Qt::transparent);
auto p = QPainter(&_badge);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(_badgeBorder, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(Qt::NoBrush);
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto smaller = QRectF(
rect.translated(-rect.topLeft())
).marginsRemoved({ half, half, half, half });
const auto radius = smaller.height() / 2.;
p.drawRoundedRect(smaller, radius, radius);
p.setPen(_badgeFg);
p.setFont(font);
p.drawText(
st::chatGiveawayBadgePadding.left(),
st::chatGiveawayBadgePadding.top() + font->ascent,
_badgeText);
}
PeerBubbleListPart::PeerBubbleListPart(
not_null<Element*> parent,
const std::vector<not_null<PeerData*>> &list)
: _parent(parent) {
for (const auto &peer : list) {
_peers.push_back({
.name = Ui::Text::String(
st::semiboldTextStyle,
peer->name(),
kDefaultTextOptions,
st::msgMinWidth),
.thumbnail = Ui::MakeUserpicThumbnail(peer),
.link = peer->openLink(),
.colorIndex = peer->colorIndex(),
});
}
}
PeerBubbleListPart::~PeerBubbleListPart() = default;
void PeerBubbleListPart::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
if (_peers.empty()) {
return;
}
const auto size = _peers[0].geometry.height();
const auto st = context.st;
const auto stm = context.messageStyle();
const auto selected = context.selected();
const auto padding = st::chatGiveawayPeerPadding;
for (const auto &peer : _peers) {
const auto &thumbnail = peer.thumbnail;
const auto &geometry = peer.geometry;
if (!_subscribed) {
thumbnail->subscribeToUpdates([=] { _parent->repaint(); });
}
const auto colorIndex = peer.colorIndex;
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get();
if (peer.corners[0].isNull() || peer.bg != cache->bg) {
peer.bg = cache->bg;
peer.corners = Images::CornersMask(size / 2);
for (auto &image : peer.corners) {
style::colorizeImage(image, cache->bg, &image);
}
}
p.setPen(cache->icon);
Ui::DrawRoundedRect(p, geometry, peer.bg, peer.corners);
if (peer.ripple) {
peer.ripple->paint(
p,
geometry.x(),
geometry.y(),
width(),
&cache->bg);
if (peer.ripple->empty()) {
peer.ripple = nullptr;
}
}
p.drawImage(geometry.topLeft(), thumbnail->image(size));
const auto left = size + padding.left();
const auto top = padding.top();
const auto available = geometry.width() - left - padding.right();
peer.name.draw(p, {
.position = { geometry.left() + left, geometry.top() + top },
.outerWidth = width(),
.availableWidth = available,
.align = style::al_left,
.palette = &stm->textPalette,
.now = context.now,
.elisionLines = 1,
.elisionBreakEverywhere = true,
});
}
_subscribed = true;
}
int PeerBubbleListPart::layout(int x, int y, int available) {
const auto size = st::chatGiveawayPeerSize;
const auto skip = st::chatGiveawayPeerSkip;
const auto padding = st::chatGiveawayPeerPadding;
auto left = available;
const auto shiftRow = [&](int i, int top, int shift) {
for (auto j = i; j != 0; --j) {
auto &geometry = _peers[j - 1].geometry;
if (geometry.top() != top) {
break;
}
geometry.moveLeft(geometry.x() + shift);
}
};
const auto count = int(_peers.size());
for (auto i = 0; i != count; ++i) {
const auto desired = size
+ padding.left()
+ _peers[i].name.maxWidth()
+ padding.right();
const auto width = std::min(desired, available);
if (left < width) {
shiftRow(i, y, (left + skip) / 2);
left = available;
y += size + skip;
}
_peers[i].geometry = { x + available - left, y, width, size };
left -= width + skip;
}
shiftRow(count, y, (left + skip) / 2);
return y + size + skip;
}
TextState PeerBubbleListPart::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
auto result = TextState(_parent);
for (const auto &peer : _peers) {
if (peer.geometry.contains(point)) {
result.link = peer.link;
_lastPoint = point;
break;
}
}
return result;
}
void PeerBubbleListPart::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (auto &peer : _peers) {
if (peer.link != p) {
continue;
}
if (pressed) {
if (!peer.ripple) {
peer.ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(
peer.geometry.size(),
peer.geometry.height() / 2),
[=] { _parent->repaint(); });
}
peer.ripple->add(_lastPoint - peer.geometry.topLeft());
} else if (peer.ripple) {
peer.ripple->lastStop();
}
break;
}
}
bool PeerBubbleListPart::hasHeavyPart() {
return _subscribed;
}
void PeerBubbleListPart::unloadHeavyPart() {
if (_subscribed) {
_subscribed = false;
for (const auto &peer : _peers) {
peer.thumbnail->subscribeToUpdates(nullptr);
}
}
}
QSize PeerBubbleListPart::countOptimalSize() {
if (_peers.empty()) {
return {};
}
const auto size = st::chatGiveawayPeerSize;
const auto skip = st::chatGiveawayPeerSkip;
const auto padding = st::chatGiveawayPeerPadding;
auto left = st::msgPadding.left();
for (const auto &peer : _peers) {
const auto desired = size
+ padding.left()
+ peer.name.maxWidth()
+ padding.right();
left += desired + skip;
}
return { left - skip + st::msgPadding.right(), size };
}
QSize PeerBubbleListPart::countCurrentSize(int newWidth) {
if (_peers.empty()) {
return {};
}
const auto padding = st::msgPadding;
const auto available = newWidth - padding.left() - padding.right();
const auto channelsBottom = layout(
padding.left(),
0,
available);
return { newWidth, channelsBottom };
}
} // namespace HistoryView

View File

@ -0,0 +1,309 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_sticker.h"
namespace Ui {
class DynamicImage;
class RippleAnimation;
} // namespace Ui
namespace HistoryView {
class MediaGeneric;
class MediaGenericPart : public Object {
public:
virtual ~MediaGenericPart() = default;
virtual void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const = 0;
[[nodiscard]] virtual TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const;
virtual void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed);
[[nodiscard]] virtual bool hasHeavyPart();
virtual void unloadHeavyPart();
[[nodiscard]] virtual auto stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements
) -> std::unique_ptr<StickerPlayer>;
};
struct MediaGenericDescriptor {
int maxWidth = 0;
bool service = false;
bool hideServiceText = false;
};
class MediaGeneric final : public Media {
public:
using Part = MediaGenericPart;
MediaGeneric(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
MediaGenericDescriptor &&descriptor = {});
~MediaGeneric();
[[nodiscard]] bool service() const {
return _service;
}
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
void clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
bool needsBubble() const override {
return !_service;
}
bool customInfoLayout() const override {
return false;
}
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override {
return true;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return true;
}
bool hideFromName() const override;
bool hideServiceText() const override;
void unloadHeavyPart() override;
bool hasHeavyPart() const override;
private:
struct Entry {
std::unique_ptr<Part> object;
};
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
[[nodiscard]] QMargins inBubblePadding() const;
std::vector<Entry> _entries;
int _maxWidthCap = 0;
bool _service : 1 = false;
bool _hideServiceText : 1 = false;
};
class MediaGenericTextPart final : public MediaGenericPart {
public:
MediaGenericTextPart(
TextWithEntities text,
QMargins margins,
const base::flat_map<uint16, ClickHandlerPtr> &links = {});
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class TextDelimeterPart final : public MediaGenericPart {
public:
TextDelimeterPart(const QString &text, QMargins margins);
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class StickerInBubblePart final : public MediaGenericPart {
public:
struct Data {
DocumentData *sticker = nullptr;
int skipTop = 0;
int size = 0;
ChatHelpers::StickerLottieSize cacheTag = {};
bool singleTimePlayback = false;
ClickHandlerPtr link;
explicit operator bool() const {
return sticker != nullptr;
}
};
StickerInBubblePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding);
[[nodiscard]] not_null<Element*> parent() const {
return _parent;
}
[[nodiscard]] bool resolved() const {
return _sticker.has_value();
}
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
private:
void ensureCreated(Element *replacing = nullptr) const;
const not_null<Element*> _parent;
Fn<Data()> _lookup;
mutable int _skipTop = 0;
mutable QMargins _padding;
mutable std::optional<Sticker> _sticker;
mutable ClickHandlerPtr _link;
};
class StickerWithBadgePart final : public MediaGenericPart {
public:
using Data = StickerInBubblePart::Data;
StickerWithBadgePart(
not_null<Element*> parent,
Element *replacing,
Fn<Data()> lookup,
QMargins padding,
QString badge);
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
private:
void validateBadge(const PaintContext &context) const;
void paintBadge(Painter &p, const PaintContext &context) const;
StickerInBubblePart _sticker;
QString _badgeText;
mutable QColor _badgeFg;
mutable QColor _badgeBorder;
mutable QImage _badge;
mutable QImage _badgeCache;
};
class PeerBubbleListPart final : public MediaGenericPart {
public:
PeerBubbleListPart(
not_null<Element*> parent,
const std::vector<not_null<PeerData*>> &list);
~PeerBubbleListPart();
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
int layout(int x, int y, int available);
struct Peer {
Ui::Text::String name;
std::shared_ptr<Ui::DynamicImage> thumbnail;
QRect geometry;
ClickHandlerPtr link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
mutable std::array<QImage, 4> corners;
mutable QColor bg;
uint8 colorIndex = 0;
};
const not_null<Element*> _parent;
std::vector<Peer> _peers;
mutable QPoint _lastPoint;
mutable bool _subscribed = false;
};
} // namespace HistoryView