/* 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_unwrapped.h" #include "history/view/media/history_view_media_common.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "lottie/lottie_single_player.h" #include "ui/cached_round_corners.h" #include "ui/chat/chat_style.h" #include "styles/style_chat.h" namespace HistoryView { namespace { constexpr auto kMaxForwardedBarLines = 4; } // namespace auto UnwrappedMedia::Content::stickerTakeLottie( not_null data, const Lottie::ColorReplacements *replacements) -> std::unique_ptr { return nullptr; } UnwrappedMedia::UnwrappedMedia( not_null parent, std::unique_ptr content) : Media(parent) , _content(std::move(content)) { } QSize UnwrappedMedia::countOptimalSize() { _content->refreshLink(); _contentSize = NonEmptySize(DownscaledSize( _content->size(), { st::maxStickerSize, st::maxStickerSize })); auto maxWidth = _contentSize.width(); const auto minimal = st::largeEmojiSize + 2 * st::largeEmojiOutline; auto minHeight = std::max(_contentSize.height(), minimal); if (_parent->media() == this) { const auto item = _parent->data(); const auto via = item->Get(); const auto reply = _parent->displayedReply(); const auto forwarded = getDisplayedForwardedInfo(); if (forwarded) { forwarded->create(via); } const auto additional = additionalWidth(via, reply, forwarded); maxWidth += additional; if (const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left())) { const auto infoHeight = st::msgDateImgPadding.y() * 2 + st::msgDateFont->height; const auto minimal = surrounding.height + st::msgDateImgDelta + infoHeight; minHeight = std::max(minHeight, minimal); } if (const auto size = _parent->rightActionSize()) { minHeight = std::max( minHeight, st::historyFastShareBottom + size->height()); } } return { maxWidth, minHeight }; } QSize UnwrappedMedia::countCurrentSize(int newWidth) { const auto item = _parent->data(); accumulate_min(newWidth, maxWidth()); if (_parent->media() == this) { const auto via = item->Get(); const auto reply = _parent->displayedReply(); const auto forwarded = getDisplayedForwardedInfo(); if (via || reply || forwarded) { int usew = maxWidth() - additionalWidth(via, reply, forwarded); int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); if (via) { via->resize(availw); } if (reply) { reply->resize(availw); } } } auto newHeight = minHeight(); if (_parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide()) { // Add some height to isolated emoji for the timestamp info. const auto infoHeight = st::msgDateImgPadding.y() * 2 + st::msgDateFont->height; const auto minimal = st::largeEmojiSize + 2 * st::largeEmojiOutline + (st::msgDateImgDelta + infoHeight); accumulate_max(newHeight, minimal); } return { newWidth, newHeight }; } void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return; } bool selected = (context.selection == FullSelection); const auto rightAligned = _parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide(); const auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : _parent->displayedReply(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } } if (rtl()) { usex = width() - usex - usew; } const auto usey = rightAligned ? 0 : (height() - _contentSize.height()); const auto useh = rightAligned ? std::max( _contentSize.height(), height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height) : _contentSize.height(); const auto inner = QRect(usex, usey, usew, useh); _content->draw(p, inner, selected); if (!inWebPage) { drawSurrounding(p, inner, context, via, reply, forwarded); } } UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, int outerw) const { if (!via && !reply && !forwarded) { return {}; } auto height = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); const auto innerw = outerw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; auto forwardedHeight = std::min( forwardedHeightReal, kMaxForwardedBarLines * st::msgServiceNameFont->height); const auto breakEverywhere = (forwardedHeightReal > forwardedHeight); if (forwarded) { height += forwardedHeight; } else if (via) { height += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { height += st::msgReplyBarSize.height(); } return { height, forwardedHeight, breakEverywhere }; } void UnwrappedMedia::drawSurrounding( Painter &p, const QRect &inner, const PaintContext &context, const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { const auto selected = context.selected(); const auto st = context.st; const auto rightAligned = _parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide(); const auto rightActionSize = _parent->rightActionSize(); const auto fullRight = calculateFullRight(inner); auto fullBottom = height(); if (needInfoDisplay()) { _parent->drawInfo( p, context, fullRight, fullBottom, inner.x() * 2 + inner.width(), InfoDisplayType::Background); } auto replyRight = 0; auto rectw = width() - inner.width() - st::msgReplyPadding.left(); if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) { auto recth = surrounding.height; int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()); int recty = 0; if (rtl()) rectx = width() - rectx - rectw; Ui::FillRoundRect(p, rectx, recty, rectw, recth, selected ? st->msgServiceBgSelected() : st->msgServiceBg(), selected ? st->msgServiceBgSelectedCorners() : st->msgServiceBgCorners()); p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); if (forwarded) { p.setTextPalette(st::serviceTextPalette); forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere); p.restoreTextPalette(); } else if (via) { p.setFont(st::msgDateFont); p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); recty += skip; } if (reply) { reply->paint(p, _parent, context, rectx, recty, rectw, false); } replyRight = rectx + rectw; } if (rightActionSize) { const auto position = calculateFastActionPosition( fullBottom, replyRight, fullRight, *rightActionSize); const auto outer = 2 * inner.x() + inner.width(); _parent->drawRightAction(p, context, position.x(), position.y(), outer); } } PointState UnwrappedMedia::pointState(QPoint point) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return PointState::Outside; } const auto rightAligned = _parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide(); const auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : _parent->displayedReply(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } } if (rtl()) { usex = width() - usex - usew; } const auto datey = height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height; const auto usey = rightAligned ? 0 : (height() - _contentSize.height()); const auto useh = rightAligned ? std::max(_contentSize.height(), datey) : _contentSize.height(); const auto inner = QRect(usex, usey, usew, useh); // Rectangle of date bubble. if (point.x() < calculateFullRight(inner) && point.y() > datey) { return PointState::Inside; } return inner.contains(point) ? PointState::Inside : PointState::Outside; } TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } const auto rightAligned = _parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide(); const auto inWebPage = (_parent->media() != this); const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : _parent->displayedReply(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } } if (rtl()) { usex = width() - usex - usew; } const auto usey = rightAligned ? 0 : (height() - _contentSize.height()); const auto useh = rightAligned ? std::max( _contentSize.height(), height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height) : _contentSize.height(); const auto inner = QRect(usex, usey, usew, useh); if (_parent->media() == this) { auto replyRight = 0; auto rectw = width() - inner.width() - st::msgReplyPadding.left(); if (auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) { auto recth = surrounding.height; int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()); int recty = 0; if (rtl()) rectx = width() - rectx - rectw; if (forwarded) { if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + surrounding.forwardedHeight).contains(point)) { auto textRequest = request.forText(); if (surrounding.forwardedBreakEverywhere) { textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere; } const auto innerw = rectw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); result = TextState(_parent, forwarded->text.getState( point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), innerw, textRequest)); result.symbol = 0; result.afterSymbol = false; if (surrounding.forwardedBreakEverywhere) { result.cursor = CursorState::Forwarded; } else { result.cursor = CursorState::None; } return result; } recty += surrounding.forwardedHeight; recth -= surrounding.forwardedHeight; } else if (via) { int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (QRect(rectx, recty, rectw, viah).contains(point)) { result.link = via->link; return result; } int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); recty += skip; recth -= skip; } if (reply) { if (QRect(rectx, recty, rectw, recth).contains(point)) { result.link = reply->replyToLink(); return result; } } replyRight = rectx + rectw - st::msgReplyPadding.right(); } const auto fullRight = calculateFullRight(inner); const auto rightActionSize = _parent->rightActionSize(); auto fullBottom = height(); if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Background)) { result.cursor = CursorState::Date; } if (rightActionSize) { const auto position = calculateFastActionPosition( fullBottom, replyRight, fullRight, *rightActionSize); if (QRect(position.x(), position.y(), rightActionSize->width(), rightActionSize->height()).contains(point)) { result.link = _parent->rightActionLink(); return result; } } } auto pixLeft = usex + (usew - _contentSize.width()) / 2; auto pixTop = (minHeight() - _contentSize.height()) / 2; // Link of content can be nullptr (e.g. sticker without stickerpack). // So we have to process it to avoid overriding the previous result. if (_content->link() && QRect({ pixLeft, pixTop }, _contentSize).contains(point)) { result.link = _content->link(); return result; } return result; } std::unique_ptr UnwrappedMedia::stickerTakeLottie( not_null data, const Lottie::ColorReplacements *replacements) { return _content->stickerTakeLottie(data, replacements); } int UnwrappedMedia::calculateFullRight(const QRect &inner) const { const auto rightAligned = _parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide(); const auto infoWidth = _parent->infoWidth() + st::msgDateImgPadding.x() * 2 + st::msgReplyPadding.left(); const auto rightActionSize = _parent->rightActionSize(); const auto rightActionWidth = rightActionSize ? (st::historyFastShareLeft * 2 + rightActionSize->width() + st::msgPadding.left() + (_parent->hasFromPhoto() ? st::msgMargin.right() : st::msgPadding.right())) : 0; auto fullRight = inner.x() + inner.width() + (rightAligned ? 0 : infoWidth); if (fullRight + rightActionWidth > _parent->width()) { fullRight = _parent->width() - rightActionWidth; } return fullRight; } QPoint UnwrappedMedia::calculateFastActionPosition( int fullBottom, int replyRight, int fullRight, QSize size) const { const auto fastShareTop = (fullBottom - st::historyFastShareBottom - size.height()); const auto doesRightActionHitReply = replyRight && (fastShareTop < st::msgReplyBarSize.height() + st::msgReplyPadding.top() + st::msgReplyPadding.bottom()); const auto fastShareLeft = ((doesRightActionHitReply ? replyRight : fullRight) + st::historyFastShareLeft); return QPoint(fastShareLeft, fastShareTop); } bool UnwrappedMedia::needInfoDisplay() const { return (_parent->data()->id < 0) || (_parent->isUnderCursor()) || (_parent->rightActionSize()) || (_parent->isLastAndSelfMessage()) || (_parent->hasOutLayout() && !_parent->delegate()->elementIsChatWide() && _content->alwaysShowOutTimestamp()); } int UnwrappedMedia::additionalWidth( const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); if (forwarded) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); } else if (via) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); } return result; } auto UnwrappedMedia::getDisplayedForwardedInfo() const -> const HistoryMessageForwarded * { return _content->hidesForwardedInfo() ? nullptr : _parent->data()->Get(); } } // namespace HistoryView