From f3662f4873ddb1e3118634392c035a85ba7758bd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Sep 2022 18:49:48 +0400 Subject: [PATCH] Support correct rounding of file thumbnails. --- .../history/history_item_components.cpp | 10 +- .../history/history_item_components.h | 29 +- .../history/view/history_view_message.cpp | 12 +- .../view/media/history_view_document.cpp | 263 ++++++++++++------ .../view/media/history_view_document.h | 19 +- .../history/view/media/history_view_media.h | 10 +- .../view/media/history_view_media_grouped.cpp | 1 + .../media/history_view_theme_document.cpp | 6 +- .../SourceFiles/ui/cached_round_corners.cpp | 23 ++ .../SourceFiles/ui/cached_round_corners.h | 13 + Telegram/SourceFiles/ui/chat/chat.style | 28 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 133 ++++++++- Telegram/SourceFiles/ui/chat/chat_style.h | 15 +- Telegram/SourceFiles/ui/chat/message_bubble.h | 67 ++++- Telegram/lib_ui | 2 +- 15 files changed, 479 insertions(+), 152 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 183185e8c..1224c6f49 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -950,7 +950,7 @@ HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=( HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default; HistoryDocumentCaptioned::HistoryDocumentCaptioned() -: _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { +: caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { } HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback( @@ -964,14 +964,14 @@ HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback( void HistoryDocumentVoice::ensurePlayback( const HistoryView::Document *that) const { - if (!_playback) { - _playback = std::make_unique(that); + if (!playback) { + playback = std::make_unique(that); } } void HistoryDocumentVoice::checkPlaybackFinished() const { - if (_playback && !_playback->progressAnimation.animating()) { - _playback.reset(); + if (playback && !playback->progressAnimation.animating()) { + playback.reset(); } } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 43e0898c9..087f97706 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "ui/empty_userpic.h" #include "ui/effects/animations.h" +#include "ui/chat/message_bubble.h" struct WebPageData; class VoiceSeekClickHandler; @@ -441,24 +442,26 @@ struct HistoryMessageLogEntryOriginal class FileClickHandler; struct HistoryDocumentThumbed : public RuntimeComponent { - std::shared_ptr _linksavel; - std::shared_ptr _linkopenwithl; - std::shared_ptr _linkcancell; - int _thumbw = 0; - - mutable int _linkw = 0; - mutable QString _link; + std::shared_ptr linksavel; + std::shared_ptr linkopenwithl; + std::shared_ptr linkcancell; + mutable QImage thumbnail; + mutable QString link; + int thumbw = 0; + mutable int linkw = 0; + mutable Ui::BubbleRounding rounding; + mutable bool blurred : 1 = false; }; struct HistoryDocumentCaptioned : public RuntimeComponent { HistoryDocumentCaptioned(); - Ui::Text::String _caption; + Ui::Text::String caption; }; struct HistoryDocumentNamed : public RuntimeComponent { - QString _name; - int _namew = 0; + QString name; + int namew = 0; }; struct HistoryDocumentVoicePlayback { @@ -477,9 +480,9 @@ public: void ensurePlayback(const HistoryView::Document *interfaces) const; void checkPlaybackFinished() const; - mutable std::unique_ptr _playback; - std::shared_ptr _seekl; - mutable int _lastDurationMs = 0; + mutable std::unique_ptr playback; + std::shared_ptr seekl; + mutable int lastDurationMs = 0; [[nodiscard]] bool seeking() const; void startSeeking(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 19c099145..b08dd7c01 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2813,11 +2813,17 @@ void Message::updateMediaInBubbleState() { ? MediaInBubbleState::Bottom : MediaInBubbleState::None; entry->setInBubbleState(entryState); - } - if (!media) { + if (!media) { + entry->setBubbleRounding(countBubbleRounding()); + return; + } + } else if (!media) { return; } + const auto guard = gsl::finally([&] { + media->setBubbleRounding(countBubbleRounding()); + }); if (!drawBubble()) { media->setInBubbleState(MediaInBubbleState::None); return; @@ -2985,7 +2991,7 @@ QRect Message::countGeometry() const { Ui::BubbleRounding Message::countBubbleRounding() const { const auto smallTop = isAttachedToPrevious(); const auto smallBottom = isAttachedToNext(); - const auto media = this->media(); + const auto media = smallBottom ? nullptr : this->media(); const auto keyboard = data()->inlineReplyKeyboard(); const auto skipTail = smallBottom || (media && media->skipBubbleTail()) diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 27276cfb8..74e83ef09 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -185,7 +185,7 @@ Document::Document( setStatusSize(Ui::FileStatusSizeReady); if (const auto captioned = Get()) { - captioned->_caption = std::move(caption); + captioned->caption = std::move(caption); } } @@ -230,13 +230,13 @@ void Document::createComponents(bool caption) { } UpdateComponents(mask); if (const auto thumbed = Get()) { - thumbed->_linksavel = std::make_shared( + thumbed->linksavel = std::make_shared( _data, _realParent->fullId()); - thumbed->_linkopenwithl = std::make_shared( + thumbed->linkopenwithl = std::make_shared( _data, _realParent->fullId()); - thumbed->_linkcancell = std::make_shared( + thumbed->linkcancell = std::make_shared( _data, crl::guard(this, [=](FullMsgId id) { _parent->delegate()->elementCancelUpload(id); @@ -244,16 +244,16 @@ void Document::createComponents(bool caption) { _realParent->fullId()); } if (const auto voice = Get()) { - voice->_seekl = std::make_shared( + voice->seekl = std::make_shared( _data, [](FullMsgId) {}); } } -void Document::fillNamedFromData(HistoryDocumentNamed *named) { - const auto nameString = named->_name = CleanTagSymbols( +void Document::fillNamedFromData(not_null named) { + const auto nameString = named->name = CleanTagSymbols( Ui::Text::FormatSongNameFor(_data).string()); - named->_namew = st::semiboldFont->width(nameString); + named->namew = st::semiboldFont->width(nameString); } QSize Document::countOptimalSize() { @@ -263,8 +263,8 @@ QSize Document::countOptimalSize() { RemoveComponents(HistoryDocumentCaptioned::Bit()); captioned = nullptr; } - } else if (captioned && captioned->_caption.hasSkipBlock()) { - captioned->_caption.updateSkipBlock( + } else if (captioned && captioned->caption.hasSkipBlock()) { + captioned->caption.updateSkipBlock( _parent->skipBlockWidth(), _parent->skipBlockHeight()); } @@ -329,9 +329,9 @@ QSize Document::countOptimalSize() { auto tw = style::ConvertScale(location.width()); auto th = style::ConvertScale(location.height()); if (tw > th) { - thumbed->_thumbw = (tw * st.thumbSize) / th; + thumbed->thumbw = (tw * st.thumbSize) / th; } else { - thumbed->_thumbw = st.thumbSize; + thumbed->thumbw = st.thumbSize; } } @@ -347,7 +347,7 @@ QSize Document::countOptimalSize() { } if (auto named = Get()) { - accumulate_max(maxWidth, tleft + named->_namew + tright); + accumulate_max(maxWidth, tleft + named->namew + tright); accumulate_min(maxWidth, st::msgMaxWidth); } if (voice && voice->transcribe) { @@ -378,7 +378,7 @@ QSize Document::countOptimalSize() { auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - minHeight += captioned->_caption.countHeight(captionw); + minHeight += captioned->caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } @@ -411,7 +411,7 @@ QSize Document::countCurrentSize(int newWidth) { } } if (captioned) { - newHeight += captioned->_caption.countHeight(captionw); + newHeight += captioned->caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); } @@ -421,14 +421,21 @@ QSize Document::countCurrentSize(int newWidth) { } void Document::draw(Painter &p, const PaintContext &context) const { - draw(p, context, width(), LayoutMode::Full); + const auto corners = (isBubbleTop() + ? (RectPart::TopLeft | RectPart::TopRight) + : RectParts()) + | ((isBubbleBottom() && !Has()) + ? (RectPart::BottomLeft | RectPart::BottomRight) + : RectParts()); + draw(p, context, width(), LayoutMode::Full, corners); } void Document::draw( Painter &p, const PaintContext &context, int width, - LayoutMode mode) const { + LayoutMode mode, + RectParts corners) const { if (width < st::msgPadding.left() + st::msgPadding.right() + 1) return; ensureDataMediaCreated(); @@ -469,25 +476,11 @@ void Document::draw( const auto inner = QRect(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize); const auto radialOpacity = radial ? _animation->radial.opacity() : 1.; if (thumbed) { - const auto inWebPage = (_parent->media() != this); - const auto args = Images::PrepareArgs{ - .options = (inWebPage - ? Images::Option::RoundSmall - : Images::Option::RoundLarge), - .outer = QSize(st.thumbSize, st.thumbSize), - }; - QPixmap thumb; - if (const auto normal = _dataMedia->thumbnail()) { - thumb = normal->pixSingle(thumbed->_thumbw, args); - } else if (const auto blurred = _dataMedia->thumbnailInline()) { - thumb = blurred->pixSingle(thumbed->_thumbw, args.blurred()); - } - p.drawPixmap(rthumb.topLeft(), thumb); + const auto rounding = thumbRounding(mode, corners); + validateThumbnail(thumbed, st.thumbSize, rounding); + p.drawImage(rthumb, thumbed->thumbnail); if (context.selected()) { - const auto st = context.st; - Ui::FillRoundRect(p, rthumb, st->msgSelectOverlay(), inWebPage - ? st->msgSelectOverlayCornersSmall() - : st->msgSelectOverlayCornersLarge()); + fillThumbnailOverlay(p, rthumb, rounding, context); } if (radial || (!loaded && !_data->loading()) || _data->waitingForAlbum()) { @@ -524,14 +517,14 @@ void Document::draw( if (_data->status != FileUploadFailed) { const auto &lnk = (_data->loading() || _data->uploading()) - ? thumbed->_linkcancell + ? thumbed->linkcancell : dataLoaded() - ? thumbed->_linkopenwithl - : thumbed->_linksavel; + ? thumbed->linkopenwithl + : thumbed->linksavel; bool over = ClickHandler::showAsActive(lnk); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(stm->msgFileThumbLinkFg); - p.drawTextLeft(nameleft, linktop, width, thumbed->_link, thumbed->_linkw); + p.drawTextLeft(nameleft, linktop, width, thumbed->link, thumbed->linkw); } } else { p.setPen(Qt::NoPen); @@ -625,21 +618,21 @@ void Document::draw( const auto progress = [&] { if (!context.outbg - && !voice->_playback + && !voice->playback && _realParent->hasUnreadMediaFlag()) { return 1.; } if (voice->seeking()) { return voice->seekingCurrent(); - } else if (voice->_playback) { - return voice->_playback->progress.current(); + } else if (voice->playback) { + return voice->playback->progress.current(); } return 0.; }(); if (voice->seeking()) { voiceStatusOverride = Ui::FormatPlayedText( - base::SafeRound(progress * voice->_lastDurationMs) / 1000, - voice->_lastDurationMs / 1000); + base::SafeRound(progress * voice->lastDurationMs) / 1000, + voice->lastDurationMs / 1000); } if (voice->transcribe) { const auto size = voice->transcribe->size(); @@ -660,10 +653,10 @@ void Document::draw( } else if (auto named = Get()) { p.setFont(st::semiboldFont); p.setPen(stm->historyFileNameFg); - if (namewidth < named->_namew) { - p.drawTextLeft(nameleft, nametop, width, st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle)); + if (namewidth < named->namew) { + p.drawTextLeft(nameleft, nametop, width, st::semiboldFont->elided(named->name, namewidth, Qt::ElideMiddle)); } else { - p.drawTextLeft(nameleft, nametop, width, named->_name, named->_namew); + p.drawTextLeft(nameleft, nametop, width, named->name, named->namew); } } @@ -695,8 +688,8 @@ void Document::draw( } if (const auto captioned = Get()) { p.setPen(stm->historyTextFg); - _parent->prepareCustomEmojiPaint(p, context, captioned->_caption); - captioned->_caption.draw(p, { + _parent->prepareCustomEmojiPaint(p, context, captioned->caption); + captioned->caption.draw(p, { .position = { st::msgPadding.left(), captiontop }, .availableWidth = captionw, .palette = &stm->textPalette, @@ -708,6 +701,91 @@ void Document::draw( } } +Ui::BubbleRounding Document::thumbRounding( + LayoutMode mode, + RectParts corners) const { + auto result = bubbleRounding(); + using Corner = Ui::BubbleCornerRounding; + if (mode != LayoutMode::Grouped && _parent->media() != this) { + return {}; // In a WebPage preview. + } + const auto adjust = [&](RectPart corner, Corner already) { + return (already == Corner::Large && (corners & corner)) + ? Corner::Large + : Corner::Small; + }; + result.topLeft = adjust(RectPart::TopLeft, result.topLeft); + result.bottomLeft = adjust(RectPart::BottomLeft, result.bottomLeft); + result.topRight = result.bottomRight = Corner::Small; + return result; +} + +void Document::validateThumbnail( + not_null thumbed, + int size, + Ui::BubbleRounding rounding) const { + const auto normal = _dataMedia->thumbnail(); + const auto blurred = _dataMedia->thumbnailInline(); + if (!normal && !blurred) { + return; + } + const auto outer = QSize(size, size); + if ((thumbed->thumbnail.size() == outer * style::DevicePixelRatio()) + && (thumbed->blurred == !normal) + && (thumbed->rounding == rounding)) { + return; + } + const auto small = (rounding == Ui::BubbleRounding()); + auto image = normal ? normal : blurred; + auto thumbnail = Images::Prepare(image->original(), thumbed->thumbw, { + .options = (normal ? Images::Option() : Images::Option::Blur) + | (small ? Images::Option::RoundSmall : Images::Option()), + .outer = outer, + }); + if (!small) { + using Corner = Ui::BubbleCornerRounding; + using Radius = Ui::CachedCornerRadius; + auto corners = std::array(); + const auto &small = Ui::CachedCornersMasks(Radius::ThumbSmall); + const auto &large = Ui::CachedCornersMasks(Radius::ThumbLarge); + for (auto i = 0; i != 4; ++i) { + switch (rounding[i]) { + case Corner::Small: corners[i] = small[i]; break; + case Corner::Large: corners[i] = large[i]; break; + } + } + thumbnail = Images::Round(std::move(thumbnail), corners); + } + thumbed->thumbnail = std::move(thumbnail); + thumbed->blurred = !normal; + thumbed->rounding = rounding; +} + +void Document::fillThumbnailOverlay( + QPainter &p, + QRect rect, + Ui::BubbleRounding rounding, + const PaintContext &context) const { + using Corner = Ui::BubbleCornerRounding; + auto corners = Ui::CornersPixmaps(); + const auto &st = context.st; + const auto set = [&](int index, const Ui::CornersPixmaps &from) { + corners.p[index] = from.p[index]; + }; + const auto lookup = [&](Corner corner) -> const Ui::CornersPixmaps & { + switch (corner) { + case Corner::None: return st->msgSelectOverlayCornersSmall(); + case Corner::Small: return st->msgSelectOverlayCornersThumbSmall(); + case Corner::Large: return st->msgSelectOverlayCornersThumbLarge(); + } + Unexpected("Corner value in Document::fillThumbnailOverlay."); + }; + for (auto i = 0; i != 4; ++i) { + corners.p[i] = lookup(rounding[i]).p[i]; + } + Ui::FillComplexOverlayRect(p, rect, st->msgSelectOverlay(), corners); +} + bool Document::hasHeavyPart() const { return (_dataMedia != nullptr); } @@ -715,7 +793,7 @@ bool Document::hasHeavyPart() const { void Document::unloadHeavyPart() { _dataMedia = nullptr; if (const auto captioned = Get()) { - captioned->_caption.unloadPersistentAnimation(); + captioned->caption.unloadPersistentAnimation(); } } @@ -867,12 +945,12 @@ TextState Document::textState( } if (_data->status != FileUploadFailed) { - if (style::rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width).contains(point)) { + if (style::rtlrect(nameleft, linktop, thumbed->linkw, st::semiboldFont->height, width).contains(point)) { result.link = (_data->loading() || _data->uploading()) - ? thumbed->_linkcancell + ? thumbed->linkcancell : dataLoaded() - ? thumbed->_linkopenwithl - : thumbed->_linksavel; + ? thumbed->linkopenwithl + : thumbed->linksavel; return result; } } @@ -910,7 +988,7 @@ TextState Document::textState( if (!voice->seeking()) { voice->setSeekingStart((point.x() - nameleft) / float64(namewidth)); } - result.link = voice->_seekl; + result.link = voice->seekl; return result; } } @@ -939,7 +1017,7 @@ TextState Document::textState( bottom += st::mediaCaptionSkip; } if (point.y() >= bottom) { - result = TextState(_parent, captioned->_caption.getState( + result = TextState(_parent, captioned->caption.getState( point - QPoint(st::msgPadding.left(), bottom), width - st::msgPadding.left() - st::msgPadding.right(), request.forText())); @@ -947,7 +1025,7 @@ TextState Document::textState( return result; } auto captionw = width - st::msgPadding.left() - st::msgPadding.right(); - painth -= captioned->_caption.countHeight(captionw); + painth -= captioned->caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } @@ -996,7 +1074,7 @@ TextSelection Document::adjustSelection( transcribe = &voice->transcribeText; } if (const auto captioned = Get()) { - caption = &captioned->_caption; + caption = &captioned->caption; } const auto transcribeLength = transcribe ? transcribe->length() : 0; if (transcribe && selection.from < transcribeLength) { @@ -1028,7 +1106,7 @@ uint16 Document::fullSelectionLength() const { result += voice->transcribeText.length(); } if (const auto captioned = Get()) { - result += captioned->_caption.length(); + result += captioned->caption.length(); } return result; } @@ -1061,7 +1139,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const { if (!result.empty()) { result.append("\n\n"); } - result.append(captioned->_caption.toTextForMimeData(selection)); + result.append(captioned->caption.toTextForMimeData(selection)); } return result; } @@ -1079,17 +1157,17 @@ void Document::setStatusSize(int64 newSize, TimeId realDuration) const { File::setStatusSize(newSize, _data->size, duration, realDuration); if (auto thumbed = Get()) { if (_statusSize == Ui::FileStatusSizeReady) { - thumbed->_link = tr::lng_media_download(tr::now).toUpper(); + thumbed->link = tr::lng_media_download(tr::now).toUpper(); } else if (_statusSize == Ui::FileStatusSizeLoaded) { - thumbed->_link = tr::lng_media_open_with(tr::now).toUpper(); + thumbed->link = tr::lng_media_open_with(tr::now).toUpper(); } else if (_statusSize == Ui::FileStatusSizeFailed) { - thumbed->_link = tr::lng_media_download(tr::now).toUpper(); + thumbed->link = tr::lng_media_download(tr::now).toUpper(); } else if (_statusSize >= 0) { - thumbed->_link = tr::lng_media_cancel(tr::now).toUpper(); + thumbed->link = tr::lng_media_cancel(tr::now).toUpper(); } else { - thumbed->_link = tr::lng_media_open_with(tr::now).toUpper(); + thumbed->link = tr::lng_media_open_with(tr::now).toUpper(); } - thumbed->_linkw = st::semiboldFont->width(thumbed->_link); + thumbed->linkw = st::semiboldFont->width(thumbed->link); } } @@ -1114,24 +1192,24 @@ bool Document::updateStatusText() const { if (state.id == AudioMsgId(_data, _realParent->fullId(), state.id.externalPlayId()) && !::Media::Player::IsStoppedOrStopping(state.state)) { if (auto voice = Get()) { - bool was = (voice->_playback != nullptr); + bool was = (voice->playback != nullptr); voice->ensurePlayback(this); - if (!was || state.position != voice->_playback->position) { + if (!was || state.position != voice->playback->position) { auto prg = state.length ? std::clamp( float64(state.position) / state.length, 0., 1.) : 0.; - if (voice->_playback->position < state.position) { - voice->_playback->progress.start(prg); + if (voice->playback->position < state.position) { + voice->playback->progress.start(prg); } else { - voice->_playback->progress = anim::value(0., prg); + voice->playback->progress = anim::value(0., prg); } - voice->_playback->position = state.position; - voice->_playback->progressAnimation.start(); + voice->playback->position = state.position; + voice->playback->progressAnimation.start(); } - voice->_lastDurationMs = static_cast((state.length * 1000LL) / state.frequency); // Bad :( + voice->lastDurationMs = static_cast((state.length * 1000LL) / state.frequency); // Bad :( } statusSize = -1 - (state.position / state.frequency); @@ -1181,7 +1259,7 @@ QSize Document::sizeForGroupingOptimal(int maxWidth) const { auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - height += captioned->_caption.countHeight(captionw); + height += captioned->caption.countHeight(captionw); } return { maxWidth, height }; } @@ -1194,7 +1272,7 @@ QSize Document::sizeForGrouping(int width) const { auto captionw = width - st::msgPadding.left() - st::msgPadding.right(); - height += captioned->_caption.countHeight(captionw); + height += captioned->caption.countHeight(captionw); } return { maxWidth(), height }; } @@ -1213,7 +1291,8 @@ void Document::drawGrouped( p, context.translated(-geometry.topLeft()), geometry.width(), - LayoutMode::Grouped); + LayoutMode::Grouped, + corners); p.translate(-geometry.topLeft()); } @@ -1235,14 +1314,14 @@ bool Document::voiceProgressAnimationCallback(crl::time now) { now += (2 * kAudioVoiceMsgUpdateView); } if (const auto voice = Get()) { - if (voice->_playback) { - const auto dt = (now - voice->_playback->progressAnimation.started()) + if (voice->playback) { + const auto dt = (now - voice->playback->progressAnimation.started()) / float64(2 * kAudioVoiceMsgUpdateView); if (dt >= 1.) { - voice->_playback->progressAnimation.stop(); - voice->_playback->progress.finish(); + voice->playback->progressAnimation.stop(); + voice->playback->progress.finish(); } else { - voice->_playback->progress.update(qMin(dt, 1.), anim::linear); + voice->playback->progress.update(qMin(dt, 1.), anim::linear); } repaint(); return (dt < 1.); @@ -1253,7 +1332,7 @@ bool Document::voiceProgressAnimationCallback(crl::time now) { void Document::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (auto voice = Get()) { - if (pressed && p == voice->_seekl && !voice->seeking()) { + if (pressed && p == voice->seekl && !voice->seeking()) { voice->startSeeking(); } else if (!pressed && voice->seeking()) { const auto type = AudioMsgId::Type::Voice; @@ -1265,8 +1344,8 @@ void Document::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed currentProgress); voice->ensurePlayback(this); - voice->_playback->position = 0; - voice->_playback->progress = anim::value(currentProgress, currentProgress); + voice->playback->position = 0; + voice->playback->progress = anim::value(currentProgress, currentProgress); } voice->stopSeeking(); } @@ -1279,14 +1358,14 @@ void Document::refreshParentId(not_null realParent) { const auto fullId = realParent->fullId(); if (auto thumbed = Get()) { - if (thumbed->_linksavel) { - thumbed->_linksavel->setMessageId(fullId); - thumbed->_linkcancell->setMessageId(fullId); + if (thumbed->linksavel) { + thumbed->linksavel->setMessageId(fullId); + thumbed->linkcancell->setMessageId(fullId); } } if (auto voice = Get()) { - if (voice->_seekl) { - voice->_seekl->setMessageId(fullId); + if (voice->seekl) { + voice->seekl->setMessageId(fullId); } } } @@ -1298,7 +1377,7 @@ void Document::parentTextUpdated() { if (!caption.isEmpty()) { AddComponents(HistoryDocumentCaptioned::Bit()); auto captioned = Get(); - captioned->_caption = std::move(caption); + captioned->caption = std::move(caption); } else { RemoveComponents(HistoryDocumentCaptioned::Bit()); } @@ -1307,7 +1386,7 @@ void Document::parentTextUpdated() { TextWithEntities Document::getCaption() const { if (const auto captioned = Get()) { - return captioned->_caption.toTextWithEntities(); + return captioned->caption.toTextWithEntities(); } return TextWithEntities(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 00dfbf57c..d45993d61 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/runtime_composer.h" struct HistoryDocumentNamed; +struct HistoryDocumentThumbed; namespace Data { class DocumentMedia; @@ -108,7 +109,8 @@ private: Painter &p, const PaintContext &context, int width, - LayoutMode mode) const; + LayoutMode mode, + RectParts corners) const; [[nodiscard]] TextState textState( QPoint point, QSize layout, @@ -122,7 +124,20 @@ private: QSize countCurrentSize(int newWidth) override; void createComponents(bool caption); - void fillNamedFromData(HistoryDocumentNamed *named); + void fillNamedFromData(not_null named); + + [[nodiscard]] Ui::BubbleRounding thumbRounding( + LayoutMode mode, + RectParts corners) const; + void validateThumbnail( + not_null thumbed, + int size, + Ui::BubbleRounding rounding) const; + void fillThumbnailOverlay( + QPainter &p, + QRect rect, + Ui::BubbleRounding rounding, + const PaintContext &context) const; void setStatusSize(int64 newSize, TimeId realDuration = 0) const; bool updateStatusText() const; // returns showPause diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index cc5a6063f..114a9f5d2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/history_view_object.h" +#include "ui/chat/message_bubble.h" #include "ui/rect_part.h" class History; @@ -45,7 +46,7 @@ class Element; using PaintContext = Ui::ChatPaintContext; -enum class MediaInBubbleState { +enum class MediaInBubbleState : uchar { None, Top, Middle, @@ -239,6 +240,12 @@ public: [[nodiscard]] MediaInBubbleState inBubbleState() const { return _inBubbleState; } + void setBubbleRounding(Ui::BubbleRounding rounding) { + _bubbleRounding = rounding; + } + [[nodiscard]] Ui::BubbleRounding bubbleRounding() const { + return _bubbleRounding; + } [[nodiscard]] bool isBubbleTop() const { return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None); @@ -318,6 +325,7 @@ protected: const not_null _parent; MediaInBubbleState _inBubbleState = MediaInBubbleState::None; + Ui::BubbleRounding _bubbleRounding; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index a2e02abdf..e1346857a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -117,6 +117,7 @@ QSize GroupedMedia::countOptimalSize() { if (_mode == Mode::Column) { for (const auto &part : _parts) { const auto &media = part.content; + media->setBubbleRounding(bubbleRounding()); media->initDimensions(); accumulate_max(maxWidth, media->maxWidth()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 5e2b060b2..45631f819 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -181,7 +181,11 @@ void ThemeDocument::draw(Painter &p, const PaintContext &context) const { validateThumbnail(); p.drawPixmap(rthumb.topLeft(), _thumbnail); if (context.selected()) { - Ui::FillComplexOverlayRect(p, st, rthumb, roundRadius, roundCorners); + Ui::FillComplexOverlayRect( + p, + rthumb, + st->msgSelectOverlay(), + st->msgSelectOverlayCornersSmall()); } if (_data) { diff --git a/Telegram/SourceFiles/ui/cached_round_corners.cpp b/Telegram/SourceFiles/ui/cached_round_corners.cpp index 81b10f412..8e2c4ea5f 100644 --- a/Telegram/SourceFiles/ui/cached_round_corners.cpp +++ b/Telegram/SourceFiles/ui/cached_round_corners.cpp @@ -19,11 +19,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { +constexpr auto kCachedCornerRadiusCount = int(CachedCornerRadius::kCount); + std::vector Corners; base::flat_map CornersMap; QImage CornersMaskLarge[4], CornersMaskSmall[4]; rpl::lifetime PaletteChangedLifetime; +std::array, kCachedCornerRadiusCount> CachedMasks; + [[nodiscard]] std::array PrepareCorners(int32 radius, const QBrush &brush, const style::color *shadow = nullptr) { int32 r = radius * style::DevicePixelRatio(), s = st::msgShadow * style::DevicePixelRatio(); QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied); @@ -215,4 +219,23 @@ void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color FillRoundRect(p, x, y, w, h, bg, i->second, nullptr, parts); } +[[nodiscard]] const std::array &CachedCornersMasks( + CachedCornerRadius radius) { + const auto index = static_cast(radius); + Assert(index >= 0 && index < kCachedCornerRadiusCount); + + if (CachedMasks[index][0].isNull()) { + using Radius = CachedCornerRadius; + const auto set = [](Radius key, int radius) { + CachedMasks[static_cast(key)] = Images::CornersMask(radius); + }; + set(Radius::Small, st::roundRadiusSmall); + set(Radius::ThumbSmall, st::msgFileThumbRadiusSmall); + set(Radius::ThumbLarge, st::msgFileThumbRadiusLarge); + set(Radius::BubbleSmall, st::bubbleRadiusSmall); + set(Radius::BubbleLarge, st::bubbleRadiusLarge); + } + return CachedMasks[index]; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/cached_round_corners.h b/Telegram/SourceFiles/ui/cached_round_corners.h index c61172b26..e2424f0f5 100644 --- a/Telegram/SourceFiles/ui/cached_round_corners.h +++ b/Telegram/SourceFiles/ui/cached_round_corners.h @@ -66,6 +66,19 @@ inline void FillRoundShadow(QPainter &p, const QRect &rect, style::color shadow, FillRoundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, corner, parts); } +enum class CachedCornerRadius { + Small, + ThumbSmall, + ThumbLarge, + BubbleSmall, + BubbleLarge, + + kCount, +}; + +[[nodiscard]] const std::array &CachedCornersMasks( + CachedCornerRadius radius); + void StartCachedCorners(); void FinishCachedCorners(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index d001afb51..8d0b47b6a 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -771,31 +771,33 @@ HistoryFileLayout { thumbSkip: pixels; } +msgFileThumbRadiusSmall: 4px; +msgFileThumbRadiusLarge: 12px; msgFileLayout: HistoryFileLayout { padding: margins(12px, 8px, 10px, 8px); - nameTop: 16px; - statusTop: 37px; + nameTop: 12px; + statusTop: 34px; thumbSize: 44px; thumbSkip: 11px; } msgFileThumbLayout: HistoryFileLayout { - padding: margins(10px, 10px, 10px, 10px); - nameTop: 12px; - statusTop: 32px; - linkTop: 60px; + padding: margins(6px, 6px, 10px, 6px); + nameTop: 11px; + statusTop: 31px; + linkTop: 55px; thumbSize: 72px; thumbSkip: 14px; } msgFileLayoutGrouped: HistoryFileLayout(msgFileLayout) { - padding: margins(14px, 7px, 11px, 7px); - nameTop: 13px; - statusTop: 34px; + padding: margins(12px, 5px, 10px, 5px); + nameTop: 9px; + statusTop: 31px; } msgFileThumbLayoutGrouped: HistoryFileLayout(msgFileThumbLayout) { - padding: margins(10px, 7px, 14px, 7px); - nameTop: 9px; - statusTop: 29px; - linkTop: 57px; + padding: margins(6px, 3px, 10px, 3px); + nameTop: 8px; + statusTop: 28px; + linkTop: 52px; } attachPreviewLayout: HistoryFileLayout { padding: margins(0px, 0px, 0px, 0px); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index bdf8027e0..c900497e3 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -493,7 +493,6 @@ void ChatStyle::assignPalette(not_null palette) { _serviceBgCornersInverted = {}; _msgBotKbOverBgAddCorners = {}; _msgSelectOverlayCornersSmall = {}; - _msgSelectOverlayCornersLarge = {}; for (auto &stm : _messageStyles) { const auto same = (stm.textPalette.linkFg->c == stm.historyTextFg->c); @@ -584,12 +583,20 @@ const CornersPixmaps &ChatStyle::msgSelectOverlayCornersSmall() const { return _msgSelectOverlayCornersSmall; } -const CornersPixmaps &ChatStyle::msgSelectOverlayCornersLarge() const { +const CornersPixmaps &ChatStyle::msgSelectOverlayCornersThumbSmall() const { EnsureCorners( - _msgSelectOverlayCornersLarge, - st::roundRadiusLarge, + _msgSelectOverlayCornersThumbSmall, + st::msgFileThumbRadiusSmall, msgSelectOverlay()); - return _msgSelectOverlayCornersLarge; + return _msgSelectOverlayCornersThumbSmall; +} + +const CornersPixmaps &ChatStyle::msgSelectOverlayCornersThumbLarge() const { + EnsureCorners( + _msgSelectOverlayCornersThumbLarge, + st::msgFileThumbRadiusLarge, + msgSelectOverlay()); + return _msgSelectOverlayCornersThumbLarge; } MessageStyle &ChatStyle::messageStyleRaw(bool outbg, bool selected) const { @@ -691,6 +698,113 @@ void ChatStyle::make( make(imageSelected().*my, originalSelected); } +void FillComplexOverlayRect( + QPainter &p, + QRect rect, + const style::color &color, + const CornersPixmaps &corners) { + constexpr auto kTopLeft = 0; + constexpr auto kTopRight = 1; + constexpr auto kBottomLeft = 2; + constexpr auto kBottomRight = 3; + + const auto pix = corners.p; + const auto fillRect = [&](QRect rect) { + p.fillRect(rect, color); + }; + if (pix[kTopLeft].isNull() + && pix[kTopRight].isNull() + && pix[kBottomLeft].isNull() + && pix[kBottomRight].isNull()) { + fillRect(rect); + return; + } + + const auto ratio = style::DevicePixelRatio(); + const auto fillCorner = [&](int left, int top, int index) { + p.drawPixmap(left, top, pix[index]); + }; + const auto cornerSize = [&](int index) { + const auto &p = pix[index]; + return p.isNull() ? 0 : p.width() / ratio; + }; + const auto verticalSkip = [&](int left, int right) { + return std::max(cornerSize(left), cornerSize(right)); + }; + const auto top = verticalSkip(kTopLeft, kTopRight); + const auto bottom = verticalSkip(kBottomLeft, kBottomRight); + if (top) { + const auto left = cornerSize(kTopLeft); + const auto right = cornerSize(kTopRight); + if (left) { + fillCorner(rect.left(), rect.top(), kTopLeft); + if (const auto add = top - left) { + fillRect({ rect.left(), rect.top() + left, left, add }); + } + } + if (const auto fill = rect.width() - left - right; fill > 0) { + fillRect({ rect.left() + left, rect.top(), fill, top }); + } + if (right) { + fillCorner( + rect.left() + rect.width() - right, + rect.top(), + kTopRight); + if (const auto add = top - right) { + fillRect({ + rect.left() + rect.width() - right, + rect.top() + right, + right, + add, + }); + } + } + } + if (const auto h = rect.height() - top - bottom; h > 0) { + fillRect({ rect.left(), rect.top() + top, rect.width(), h }); + } + if (bottom) { + const auto left = cornerSize(kBottomLeft); + const auto right = cornerSize(kBottomRight); + if (left) { + fillCorner( + rect.left(), + rect.top() + rect.height() - left, + kBottomLeft); + if (const auto add = bottom - left) { + fillRect({ + rect.left(), + rect.top() + rect.height() - bottom, + left, + add, + }); + } + } + if (const auto fill = rect.width() - left - right; fill > 0) { + fillRect({ + rect.left() + left, + rect.top() + rect.height() - bottom, + fill, + bottom, + }); + } + if (right) { + fillCorner( + rect.left() + rect.width() - right, + rect.top() + rect.height() - right, + kBottomRight); + if (const auto add = bottom - right) { + fillRect({ + rect.left() + rect.width() - right, + rect.top() + rect.height() - bottom, + right, + add, + }); + } + } + } +} + void FillComplexOverlayRect( QPainter &p, not_null st, @@ -704,10 +818,11 @@ void FillComplexOverlayRect( p.setBrush(bg); p.drawEllipse(rect); } else { - const auto &corners = (radius == ImageRoundRadius::Small) - ? st->msgSelectOverlayCornersSmall() - : st->msgSelectOverlayCornersLarge(); - RectWithCorners(p, rect, bg, corners, roundCorners); + // #TODO rounding + //const auto &corners = (radius == ImageRoundRadius::Small) + // ? st->msgSelectOverlayCornersSmall() + // : st->msgSelectOverlayCornersLarge(); + //RectWithCorners(p, rect, bg, corners, roundCorners); } } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index cc9b9cf8b..a9dbbcd26 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/cached_round_corners.h" +#include "ui/chat/message_bubble.h" #include "ui/style/style_core_palette.h" #include "layout/layout_selection.h" #include "styles/style_basic.h" @@ -192,7 +193,10 @@ public: [[nodiscard]] const CornersPixmaps &msgBotKbOverBgAddCorners() const; [[nodiscard]] const CornersPixmaps &msgSelectOverlayCornersSmall() const; - [[nodiscard]] const CornersPixmaps &msgSelectOverlayCornersLarge() const; + [[nodiscard]] auto msgSelectOverlayCornersThumbSmall() const + -> const CornersPixmaps &; + [[nodiscard]] auto msgSelectOverlayCornersThumbLarge() const + -> const CornersPixmaps &; [[nodiscard]] const style::TextPalette &historyPsaForwardPalette() const { return _historyPsaForwardPalette; @@ -319,7 +323,8 @@ private: mutable CornersPixmaps _msgBotKbOverBgAddCorners; mutable CornersPixmaps _msgSelectOverlayCornersSmall; - mutable CornersPixmaps _msgSelectOverlayCornersLarge; + mutable CornersPixmaps _msgSelectOverlayCornersThumbSmall; + mutable CornersPixmaps _msgSelectOverlayCornersThumbLarge; style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; @@ -353,6 +358,12 @@ private: }; +void FillComplexOverlayRect( + QPainter &p, + QRect rect, + const style::color &color, + const CornersPixmaps &corners); + void FillComplexOverlayRect( QPainter &p, not_null st, diff --git a/Telegram/SourceFiles/ui/chat/message_bubble.h b/Telegram/SourceFiles/ui/chat/message_bubble.h index e47bb66a3..57fd49adf 100644 --- a/Telegram/SourceFiles/ui/chat/message_bubble.h +++ b/Telegram/SourceFiles/ui/chat/message_bubble.h @@ -7,27 +7,74 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/rect_part.h" - -class Painter; - namespace Ui { class ChatTheme; class ChatStyle; enum class BubbleCornerRounding : uchar { - Large, - Small, None, Tail, + Small, + Large, }; struct BubbleRounding { - BubbleCornerRounding topLeft = BubbleCornerRounding(); - BubbleCornerRounding topRight = BubbleCornerRounding(); - BubbleCornerRounding bottomLeft = BubbleCornerRounding(); - BubbleCornerRounding bottomRight = BubbleCornerRounding(); + BubbleCornerRounding topLeft : 2 = BubbleCornerRounding(); + BubbleCornerRounding topRight : 2 = BubbleCornerRounding(); + BubbleCornerRounding bottomLeft : 2 = BubbleCornerRounding(); + BubbleCornerRounding bottomRight : 2 = BubbleCornerRounding(); + + struct ConstProxy { + constexpr ConstProxy( + not_null that, + int index) noexcept + : that(that) + , index(index) { + Expects(index >= 0 && index < 4); + } + + constexpr operator BubbleCornerRounding() const noexcept { + switch (index) { + case 0: return that->topLeft; + case 1: return that->topRight; + case 2: return that->bottomLeft; + case 3: return that->bottomRight; + } + Unexpected("Index value in BubbleRounding::ConstProxy."); + } + + not_null that; + int index = 0; + }; + struct Proxy : ConstProxy { + constexpr Proxy(not_null that, int index) noexcept + : ConstProxy(that, index) { + } + + using ConstProxy::operator BubbleCornerRounding; + + constexpr Proxy &operator=(BubbleCornerRounding value) noexcept { + const auto nonconst = const_cast(that.get()); + switch (index) { + case 0: nonconst->topLeft = value; break; + case 1: nonconst->topRight = value; break; + case 2: nonconst->bottomLeft = value; break; + case 3: nonconst->bottomRight = value; break; + } + return *this; + } + }; + [[nodiscard]] constexpr ConstProxy operator[](int index) const { + return { this, index }; + } + [[nodiscard]] constexpr Proxy operator[](int index) { + return { this, index }; + } + + inline friend constexpr auto operator<=>( + BubbleRounding, + BubbleRounding) = default; }; struct BubbleSelectionInterval { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 80445f2bd..f49ec866c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 80445f2bd3ebb377b990d2a471021e906e611240 +Subproject commit f49ec866c11fc887d9b16435f67f756d523a9b5b