diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a0a500bff6..1fecca3543 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 3e43cd47af..f82b4522f3 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -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 MediaGiveawayStart::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique( + return std::make_unique( message, HistoryView::GenerateGiveawayStart(message, &_data)); } @@ -2370,7 +2371,7 @@ std::unique_ptr MediaGiveawayResults::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique( + return std::make_unique( message, HistoryView::GenerateGiveawayResults(message, &_data)); } diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index e2f5083fcb..9d1cb9ed27 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -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)> helloChosen) --> Fn)>)> { - return [=](Fn)> push) { +-> Fn)>)> { + return [=](Fn)> push) { auto pushText = [&]( TextWithEntities text, QMargins margins = {}, @@ -88,7 +88,7 @@ auto GenerateChatIntro( if (text.empty()) { return; } - push(std::make_unique( + push(std::make_unique( std::move(text), margins, links)); @@ -287,10 +287,10 @@ void AboutView::make(Data::ChatIntro data) { const auto helloChosen = [=](not_null sticker) { setHelloChosen(sticker); }; - owned->overrideMedia(std::make_unique( + owned->overrideMedia(std::make_unique( owned.get(), GenerateChatIntro(owned.get(), _item.get(), data, helloChosen), - HistoryView::MediaInBubbleDescriptor{ + HistoryView::MediaGenericDescriptor{ .maxWidth = st::chatIntroWidth, .service = true, .hideServiceText = true, diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 041b1c256c..310af8fa6a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -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 data, - const Lottie::ColorReplacements *replacements -) -> std::unique_ptr { - return nullptr; -} - -MediaInBubble::MediaInBubble( - not_null parent, - Fn)>)> generate, - MediaInBubbleDescriptor &&descriptor) -: Media(parent) -, _maxWidthCap(descriptor.maxWidth) -, _service(descriptor.service) -, _hideServiceText(descriptor.hideServiceText) { - generate([&](std::unique_ptr 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 MediaInBubble::stickerTakePlayer( - not_null 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(); -} - -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 &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 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 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 parent, - Element *replacing, - Fn lookup, - QMargins padding) -: _parent(parent) -, _lookup(std::move(lookup)) -, _padding(padding) { - ensureCreated(replacing); -} - -void StickerInBubblePart::draw( - Painter &p, - not_null 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 StickerInBubblePart::stickerTakePlayer( - not_null 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 parent, - Element *replacing, - Fn lookup, - QMargins padding, - QString badge) -: _sticker(parent, replacing, std::move(lookup), padding) -, _badgeText(badge) { -} - -void StickerWithBadgePart::draw( - Painter &p, - not_null 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 StickerWithBadgePart::stickerTakePlayer( - not_null 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 parent, - const std::vector> &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 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( - 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 parent, not_null data) --> Fn)>)> { - return [=](Fn)> push) { +-> Fn)>)> { + return [=](Fn)> 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 &links = {}) { - push(std::make_unique( + push(std::make_unique( std::move(text), margins, links)); @@ -907,8 +166,8 @@ auto GenerateGiveawayStart( auto GenerateGiveawayResults( not_null parent, not_null data) --> Fn)>)> { - return [=](Fn)> push) { +-> Fn)>)> { + return [=](Fn)> push) { const auto quantity = data->winnersCount; using Data = StickerWithBadgePart::Data; @@ -936,7 +195,7 @@ auto GenerateGiveawayResults( TextWithEntities text, QMargins margins = {}, const base::flat_map &links = {}) { - push(std::make_unique( + push(std::make_unique( std::move(text), margins, links)); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 2efe86d59c..23f223bd29 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -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 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 data, - const Lottie::ColorReplacements *replacements - ) -> std::unique_ptr; - }; - - MediaInBubble( - not_null parent, - Fn)>)> 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 stickerTakePlayer( - not_null 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 object; - }; - - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - [[nodiscard]] QMargins inBubblePadding() const; - - std::vector _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 &links = {}); - - void draw( - Painter &p, - not_null 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 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 parent, - Element *replacing, - Fn lookup, - QMargins padding); - - [[nodiscard]] not_null parent() const { - return _parent; - } - [[nodiscard]] bool resolved() const { - return _sticker.has_value(); - } - - void draw( - Painter &p, - not_null 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 stickerTakePlayer( - not_null data, - const Lottie::ColorReplacements *replacements) override; - -private: - void ensureCreated(Element *replacing = nullptr) const; - - const not_null _parent; - Fn _lookup; - mutable int _skipTop = 0; - mutable QMargins _padding; - mutable std::optional _sticker; - mutable ClickHandlerPtr _link; - -}; - -class StickerWithBadgePart final : public MediaInBubble::Part { -public: - using Data = StickerInBubblePart::Data; - StickerWithBadgePart( - not_null parent, - Element *replacing, - Fn lookup, - QMargins padding, - QString badge); - - void draw( - Painter &p, - not_null 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 stickerTakePlayer( - not_null 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 parent, - const std::vector> &list); - ~PeerBubbleListPart(); - - void draw( - Painter &p, - not_null 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 thumbnail; - QRect geometry; - ClickHandlerPtr link; - mutable std::unique_ptr ripple; - mutable std::array corners; - mutable QColor bg; - uint8 colorIndex = 0; - }; - - const not_null _parent; - std::vector _peers; - mutable QPoint _lastPoint; - mutable bool _subscribed = false; - -}; +class Element; +class MediaGenericPart; [[nodiscard]] auto GenerateGiveawayStart( not_null parent, not_null data) --> Fn)>)>; +-> Fn)>)>; [[nodiscard]] auto GenerateGiveawayResults( not_null parent, not_null data) --> Fn)>)>; +-> Fn)>)>; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp new file mode 100644 index 0000000000..98c3616a83 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -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 data, + const Lottie::ColorReplacements *replacements +) -> std::unique_ptr { + return nullptr; +} + +MediaGeneric::MediaGeneric( + not_null parent, + Fn)>)> generate, + MediaGenericDescriptor &&descriptor) +: Media(parent) +, _maxWidthCap(descriptor.maxWidth) +, _service(descriptor.service) +, _hideServiceText(descriptor.hideServiceText) { + generate([&](std::unique_ptr 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 MediaGeneric::stickerTakePlayer( + not_null 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(); +} + +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 &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 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 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 parent, + Element *replacing, + Fn lookup, + QMargins padding) +: _parent(parent) +, _lookup(std::move(lookup)) +, _padding(padding) { + ensureCreated(replacing); +} + +void StickerInBubblePart::draw( + Painter &p, + not_null 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 StickerInBubblePart::stickerTakePlayer( + not_null 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 parent, + Element *replacing, + Fn lookup, + QMargins padding, + QString badge) +: _sticker(parent, replacing, std::move(lookup), padding) +, _badgeText(badge) { +} + +void StickerWithBadgePart::draw( + Painter &p, + not_null 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 StickerWithBadgePart::stickerTakePlayer( + not_null 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 parent, + const std::vector> &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 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( + 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 diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h new file mode 100644 index 0000000000..0d16bf40d0 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -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 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 data, + const Lottie::ColorReplacements *replacements + ) -> std::unique_ptr; +}; + +struct MediaGenericDescriptor { + int maxWidth = 0; + bool service = false; + bool hideServiceText = false; +}; + +class MediaGeneric final : public Media { +public: + using Part = MediaGenericPart; + + MediaGeneric( + not_null parent, + Fn)>)> 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 stickerTakePlayer( + not_null 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 object; + }; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + [[nodiscard]] QMargins inBubblePadding() const; + + std::vector _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 &links = {}); + + void draw( + Painter &p, + not_null 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 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 parent, + Element *replacing, + Fn lookup, + QMargins padding); + + [[nodiscard]] not_null parent() const { + return _parent; + } + [[nodiscard]] bool resolved() const { + return _sticker.has_value(); + } + + void draw( + Painter &p, + not_null 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 stickerTakePlayer( + not_null data, + const Lottie::ColorReplacements *replacements) override; + +private: + void ensureCreated(Element *replacing = nullptr) const; + + const not_null _parent; + Fn _lookup; + mutable int _skipTop = 0; + mutable QMargins _padding; + mutable std::optional _sticker; + mutable ClickHandlerPtr _link; + +}; + +class StickerWithBadgePart final : public MediaGenericPart { +public: + using Data = StickerInBubblePart::Data; + StickerWithBadgePart( + not_null parent, + Element *replacing, + Fn lookup, + QMargins padding, + QString badge); + + void draw( + Painter &p, + not_null 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 stickerTakePlayer( + not_null 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 parent, + const std::vector> &list); + ~PeerBubbleListPart(); + + void draw( + Painter &p, + not_null 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 thumbnail; + QRect geometry; + ClickHandlerPtr link; + mutable std::unique_ptr ripple; + mutable std::array corners; + mutable QColor bg; + uint8 colorIndex = 0; + }; + + const not_null _parent; + std::vector _peers; + mutable QPoint _lastPoint; + mutable bool _subscribed = false; + +}; + +} // namespace HistoryView