diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 93e291f615..a142eeb02e 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -589,11 +589,18 @@ QRect BackgroundPreviewBox::radialRect() const { void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { const auto height1 = _text1->height(); const auto height2 = _text2->height(); + const auto context = HistoryView::PaintContext{ + .bubblesPattern = nullptr, // #TODO bubbles + .viewport = rect(), + .clip = rect(), + .selection = TextSelection(), + .now = ms, + }; p.translate(0, textsTop()); paintDate(p); - _text1->draw(p, rect(), TextSelection(), ms); + _text1->draw(p, context); p.translate(0, height1); - _text2->draw(p, rect(), TextSelection(), ms); + _text2->draw(p, context); p.translate(0, height2); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 1ea5463f7b..28161ff97b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -895,9 +895,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Painter p(this); - auto ms = crl::now(); auto clip = e->rect(); - if (_items.empty() && _upLoaded && _downLoaded) { paintEmpty(p); } else { @@ -914,16 +912,26 @@ void InnerWidget::paintEvent(QPaintEvent *e) { return this->itemTop(elem) < bottom; }); if (from != end) { + auto viewport = QRect(); // #TODO bubbles auto top = itemTop(from->get()); + auto context = HistoryView::PaintContext{ + .bubblesPattern = nullptr, + .viewport = viewport.translated(0, -top), + .clip = clip.translated(0, -top), + .now = crl::now(), + }; p.translate(0, top); for (auto i = from; i != to; ++i) { const auto view = i->get(); - const auto selection = (view == _selectedItem) + context.selection = (view == _selectedItem) ? _selectedText : TextSelection(); - view->draw(p, clip.translated(0, -top), selection, ms); - auto height = view->height(); + view->draw(p, context); + + const auto height = view->height(); top += height; + context.viewport.translate(0, -height); + context.clip.translate(0, -height); p.translate(0, height); } p.translate(0, -top); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 089fcf6ef8..e89dd88588 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -560,7 +560,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); - auto ms = crl::now(); const auto historyDisplayedEmpty = _history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty()); @@ -602,6 +601,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } else { seltoy += _dragSelTo->height(); } + const auto visibleAreaTopGlobal = mapToGlobal( + QPoint(0, _visibleAreaTop)).y(); auto mtop = migratedTop(); auto htop = historyTop(); @@ -613,15 +614,20 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto view = block->messages[iItem].get(); auto item = view->data(); - auto y = mtop + block->y() + view->y(); - p.save(); - p.translate(0, y); - if (clip.y() < y + view->height()) while (y < drawToY) { - const auto selection = itemRenderSelection( + auto top = mtop + block->y() + view->y(); + auto context = _controller->bubblesContext({ + .visibleAreaTop = _visibleAreaTop, + .visibleAreaTopGlobal = visibleAreaTopGlobal, + .clip = clip, + .initialShift = top, + }); + p.translate(0, top); + if (context.clip.y() < view->height()) while (top < drawToY) { + context.selection = itemRenderSelection( view, selfromy - mtop, seltoy - mtop); - view->draw(p, clip.translated(0, -y), selection, ms); + view->draw(p, context); if (item->hasViews()) { _controller->content()->scheduleViewIncrement(item); @@ -631,9 +637,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _widget->enqueueMessageHighlight(view); } - int32 h = view->height(); - p.translate(0, h); - y += h; + const auto height = view->height(); + top += height; + context.viewport.translate(0, -height); + context.clip.translate(0, -height); + p.translate(0, height); ++iItem; if (iItem == block->messages.size()) { @@ -647,7 +655,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view = block->messages[iItem].get(); item = view->data(); } - p.restore(); + p.translate(0, -top); } if (htop >= 0) { auto iBlock = (_curHistory == _history ? _curBlock : 0); @@ -656,21 +664,28 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto view = block->messages[iItem].get(); auto item = view->data(); auto readTill = (HistoryItem*)nullptr; - auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); - auto y = htop + block->y() + view->y(); - p.save(); - p.translate(0, y); - while (y < drawToY) { - const auto h = view->height(); - if (hclip.y() < y + h && hdrawtop < y + h) { - const auto selection = itemRenderSelection( + auto top = htop + block->y() + view->y(); + auto context = _controller->bubblesContext({ + .visibleAreaTop = _visibleAreaTop, + .visibleAreaTopGlobal = visibleAreaTopGlobal, + .visibleAreaWidth = width(), + .clip = clip.intersected( + QRect(0, hdrawtop, width(), clip.top() + clip.height()) + ), + .initialShift = top, + }); + p.translate(0, top); + while (top < drawToY) { + const auto height = view->height(); + if (context.clip.y() < height && hdrawtop < top + height) { + context.selection = itemRenderSelection( view, selfromy - htop, seltoy - htop); - view->draw(p, hclip.translated(0, -y), selection, ms); + view->draw(p, context); - const auto middle = y + h / 2; - const auto bottom = y + h; + const auto middle = top + height / 2; + const auto bottom = top + height; if (_visibleAreaBottom >= bottom) { const auto item = view->data(); if (!item->out() && item->unread()) { @@ -688,8 +703,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } } } - p.translate(0, h); - y += h; + top += height; + context.viewport.translate(0, -height); + context.clip.translate(0, -height); + p.translate(0, height); ++iItem; if (iItem == block->messages.size()) { @@ -703,7 +720,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view = block->messages[iItem].get(); item = view->data(); } - p.restore(); + p.translate(0, -top); if (readTill && _widget->doWeReadServerHistory()) { session().data().histories().readInboxTill(readTill); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3ba6cc0872..3b3ce6b043 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -24,6 +24,7 @@ class SessionController; namespace Ui { class PathShiftGradient; +struct BubblePattern; } // namespace Ui namespace HistoryView { @@ -191,6 +192,14 @@ struct DateBadge : public RuntimeComponent { }; +struct PaintContext { + const Ui::BubblePattern *bubblesPattern = nullptr; + QRect viewport; + QRect clip; + TextSelection selection; + crl::time now = 0; +}; + class Element : public Object , public RuntimeComposer @@ -261,11 +270,7 @@ public: bool displayDate() const; bool isInOneDayWithPrevious() const; - virtual void draw( - Painter &p, - QRect clip, - TextSelection selection, - crl::time ms) const = 0; + virtual void draw(Painter &p, const PaintContext &context) const = 0; [[nodiscard]] virtual PointState pointState(QPoint point) const = 0; [[nodiscard]] virtual TextState textState( QPoint point, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 83848a2834..dc7a8f1b7f 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1612,17 +1612,22 @@ void ListWidget::paintEvent(QPaintEvent *e) { return this->itemTop(elem) < bottom; }); if (from != end(_items)) { + auto viewport = QRect(); // #TODO bubbles auto top = itemTop(from->get()); + auto context = HistoryView::PaintContext{ + .bubblesPattern = nullptr, + .viewport = viewport.translated(0, -top), + .clip = clip.translated(0, -top), + .now = crl::now(), + }; p.translate(0, top); for (auto i = from; i != to; ++i) { const auto view = *i; - view->draw( - p, - clip.translated(0, -top), - itemRenderSelection(view), - ms); + view->draw(p, context); const auto height = view->height(); top += height; + context.viewport.translate(0, -height); + context.clip.translate(0, -height); p.translate(0, height); } p.translate(0, -top); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 0bf1cc8ae7..c046475067 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "ui/effects/ripple_animation.h" #include "base/unixtime.h" +#include "ui/chat/message_bubble.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" #include "ui/text/text_entity.h" @@ -149,103 +150,6 @@ QString FastReplyText() { return tr::lng_fast_reply(tr::now); } -void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide, RectParts skip) { - auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg); - auto sh = &(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow)); - auto cors = selected ? (outbg ? Ui::MessageOutSelectedCorners : Ui::MessageInSelectedCorners) : (outbg ? Ui::MessageOutCorners : Ui::MessageInCorners); - auto parts = RectPart::None | RectPart::NoTopBottom; - if (skip & RectPart::Top) { - if (skip & RectPart::Bottom) { - p.fillRect(rect, bg); - return; - } - rect.setTop(rect.y() - st::historyMessageRadius); - } else { - parts |= RectPart::FullTop; - } - if (skip & RectPart::Bottom) { - rect.setHeight(rect.height() + st::historyMessageRadius); - sh = nullptr; - tailSide = RectPart::None; - } else { - parts |= RectPart::Bottom; - } - if (tailSide == RectPart::Right) { - parts |= RectPart::BottomLeft; - p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); - auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight; - tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth); - p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh); - } else if (tailSide == RectPart::Left) { - parts |= RectPart::BottomRight; - p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); - auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft); - tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth); - p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh); - } else if (!(skip & RectPart::Bottom)) { - parts |= RectPart::FullBottom; - } - Ui::FillRoundRect(p, rect, bg, cors, sh, parts); -} - -void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, const std::vector &selection, bool outbg, RectPart tailSide) { - if (selection.empty()) { - PaintBubble( - p, - rect, - outerWidth, - selected, - outbg, - tailSide, - RectPart::None); - return; - } - const auto left = rect.x(); - const auto width = rect.width(); - const auto top = rect.y(); - const auto bottom = top + rect.height(); - auto from = top; - for (const auto &selected : selection) { - if (selected.top > from) { - const auto skip = RectPart::Bottom - | (from > top ? RectPart::Top : RectPart::None); - PaintBubble( - p, - QRect(left, from, width, selected.top - from), - outerWidth, - false, - outbg, - tailSide, - skip); - } - const auto skip = ((selected.top > top) - ? RectPart::Top - : RectPart::None) - | ((selected.top + selected.height < bottom) - ? RectPart::Bottom - : RectPart::None); - PaintBubble( - p, - QRect(left, selected.top, width, selected.height), - outerWidth, - true, - outbg, - tailSide, - skip); - from = selected.top + selected.height; - } - if (from < bottom) { - PaintBubble( - p, - QRect(left, from, width, bottom - from), - outerWidth, - false, - outbg, - tailSide, - RectPart::Top); - } -} - style::color FromNameFg(PeerId peerId, bool selected) { if (selected) { const style::color colors[] = { @@ -545,11 +449,7 @@ int Message::marginBottom() const { return isHidden() ? 0 : st::msgMargin.bottom(); } -void Message::draw( - Painter &p, - QRect clip, - TextSelection selection, - crl::time ms) const { +void Message::draw(Painter &p, const PaintContext &context) const { auto g = countGeometry(); if (g.width() < 1) { return; @@ -560,7 +460,7 @@ void Message::draw( const auto outbg = hasOutLayout(); const auto bubble = drawBubble(); - const auto selected = (selection == FullSelection); + const auto selected = (context.selection == FullSelection); auto dateh = 0; if (const auto date = Get()) { @@ -568,7 +468,7 @@ void Message::draw( } if (const auto bar = Get()) { auto unreadbarh = bar->height(); - if (clip.intersects(QRect(0, dateh, width(), unreadbarh))) { + if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) { p.translate(0, dateh); bar->paint(p, 0, width(), delegate()->elementIsChatWide()); p.translate(0, -dateh); @@ -587,8 +487,8 @@ void Message::draw( auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto mediaSelectionIntervals = (!selected && mediaDisplayed) - ? media->getBubbleSelectionIntervals(selection) - : std::vector(); + ? media->getBubbleSelectionIntervals(context.selection) + : std::vector(); auto localMediaTop = 0; const auto customHighlight = mediaDisplayed && media->customHighlight(); if (!mediaSelectionIntervals.empty() || customHighlight) { @@ -633,7 +533,7 @@ void Message::draw( g.setHeight(g.height() - keyboardHeight); auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin); p.translate(keyboardPosition); - keyboard->paint(p, g.width(), clip.translated(-keyboardPosition)); + keyboard->paint(p, g.width(), context.clip.translated(-keyboardPosition)); p.translate(-keyboardPosition); } @@ -644,23 +544,30 @@ void Message::draw( fromNameUpdated(g.width()); } - auto skipTail = isAttachedToNext() + const auto skipTail = isAttachedToNext() || (media && media->skipBubbleTail()) || (keyboard != nullptr) - || (context() == Context::Replies && data()->isDiscussionPost()); - auto displayTail = skipTail + || (this->context() == Context::Replies + && data()->isDiscussionPost()); + const auto displayTail = skipTail ? RectPart::None : (outbg && !delegate()->elementIsChatWide()) ? RectPart::Right : RectPart::Left; - PaintBubble( + Ui::PaintBubble( p, - g, - width(), - selected, - mediaSelectionIntervals, - outbg, - displayTail); + Ui::ComplexBubble{ + .simple = Ui::SimpleBubble{ + .geometry = g, + .pattern = context.bubblesPattern, + .patternViewport = context.viewport, + .outerWidth = width(), + .selected = selected, + .outbg = outbg, + .tailSide = displayTail, + }, + .selection = mediaSelectionIntervals, + }); auto inner = g; paintCommentsButton(p, inner, selected); @@ -680,25 +587,32 @@ void Message::draw( if (entry) { trect.setHeight(trect.height() - entry->height()); } - paintText(p, trect, selection); + paintText(p, trect, context.selection); if (mediaDisplayed) { auto mediaHeight = media->height(); auto mediaLeft = inner.left(); auto mediaTop = (trect.y() + trect.height() - mediaHeight); p.translate(mediaLeft, mediaTop); - media->draw(p, clip.translated(-mediaLeft, -mediaTop), skipTextSelection(selection), ms); + media->draw( + p, + context.clip.translated(-mediaLeft, -mediaTop), + skipTextSelection(context.selection), context.now); p.translate(-mediaLeft, -mediaTop); } if (entry) { auto entryLeft = inner.left(); auto entryTop = trect.y() + trect.height(); p.translate(entryLeft, entryTop); - auto entrySelection = skipTextSelection(selection); + auto entrySelection = skipTextSelection(context.selection); if (mediaDisplayed) { entrySelection = media->skipSelection(entrySelection); } - entry->draw(p, clip.translated(-entryLeft, -entryTop), entrySelection, ms); + entry->draw( + p, + context.clip.translated(-entryLeft, -entryTop), + entrySelection, + context.now); p.translate(-entryLeft, -entryTop); } const auto needDrawInfo = entry @@ -734,11 +648,15 @@ void Message::draw( } if (media) { - media->paintBubbleFireworks(p, g, ms); + media->paintBubbleFireworks(p, g, context.now); } } else if (media && media->isDisplayed()) { p.translate(g.topLeft()); - media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms); + media->draw( + p, + context.clip.translated(-g.topLeft()), + skipTextSelection(context.selection), + context.now); p.translate(-g.topLeft()); } diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 0d61e61d7d..0cb41cf53b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -51,11 +51,7 @@ public: int marginTop() const override; int marginBottom() const override; - void draw( - Painter &p, - QRect clip, - TextSelection selection, - crl::time ms) const override; + void draw(Painter &p, const PaintContext &context) const override; PointState pointState(QPoint point) const override; TextState textState( QPoint point, diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 4133900ab3..961189674a 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -519,11 +519,7 @@ int Service::marginBottom() const { return st::msgServiceMargin.bottom(); } -void Service::draw( - Painter &p, - QRect clip, - TextSelection selection, - crl::time ms) const { +void Service::draw(Painter &p, const PaintContext &context) const { const auto item = message(); auto g = countGeometry(); if (g.width() < 1) { @@ -533,6 +529,7 @@ void Service::draw( auto height = this->height() - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); auto dateh = 0; auto unreadbarh = 0; + auto clip = context.clip; if (auto date = Get()) { dateh = date->height(); p.translate(0, dateh); @@ -564,7 +561,7 @@ void Service::draw( height -= st::msgServiceMargin.top() + media->height(); auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); p.translate(left, top); - media->draw(p, clip.translated(-left, -top), TextSelection(), ms); + media->draw(p, clip.translated(-left, -top), TextSelection(), context.now); p.translate(-left, -top); } @@ -575,7 +572,7 @@ void Service::draw( p.setBrush(Qt::NoBrush); p.setPen(st::msgServiceFg); p.setFont(st::msgServiceFont); - item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection, false); + item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, context.selection, false); p.restoreTextPalette(); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index 3212222252..4d26eecb7d 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -23,11 +23,7 @@ public: int marginTop() const override; int marginBottom() const override; bool isHidden() const override; - void draw( - Painter &p, - QRect clip, - TextSelection selection, - crl::time ms) const override; + void draw(Painter &p, const PaintContext &context) const override; PointState pointState(QPoint point) const override; TextState textState( QPoint point, @@ -50,17 +46,6 @@ private: int WideChatWidth(); -struct PaintContext { - PaintContext(crl::time ms, const QRect &clip, TextSelection selection) - : ms(ms) - , clip(clip) - , selection(selection) { - } - crl::time ms; - const QRect &clip; - TextSelection selection; -}; - class ServiceMessagePainter { public: static void paintDate( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index bcfc800f93..eeb3c8a3e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "data/data_document.h" #include "ui/item_text_options.h" +#include "ui/chat/message_bubble.h" #include "core/ui_integration.h" #include "styles/style_chat.h" @@ -166,6 +167,13 @@ TextSelection Media::unskipSelection(TextSelection selection) const { return ShiftItemSelection(selection, fullSelectionLength()); } +auto Media::getBubbleSelectionIntervals( + TextSelection selection) const +-> std::vector { + return {}; +} + + PointState Media::pointState(QPoint point) const { return QRect(0, 0, width(), height()).contains(point) ? PointState::Inside diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 5dc2a84f10..d910e00864 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -29,6 +29,10 @@ class SinglePlayer; struct ColorReplacements; } // namespace Lottie +namespace Ui { +struct BubbleSelectionInterval; +} // namespace Ui + namespace HistoryView { enum class PointState : char; @@ -45,11 +49,6 @@ enum class MediaInBubbleState { Bottom, }; -struct BubbleSelectionInterval { - int top = 0; - int height = 0; -}; - [[nodiscard]] QString DocumentTimestampLinkBase( not_null document, FullMsgId context); @@ -125,9 +124,7 @@ public: [[nodiscard]] virtual auto getBubbleSelectionIntervals( TextSelection selection) const - -> std::vector { - return {}; - } + -> std::vector; // if we press and drag this link should we drag the item [[nodiscard]] virtual bool dragItemByHandler( 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 721684fb07..a0da973d4e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" #include "ui/grouped_layout.h" +#include "ui/chat/message_bubble.h" #include "ui/text/text_options.h" #include "layout/layout_selection.h" #include "styles/style_chat.h" @@ -509,8 +510,8 @@ TextForMimeData GroupedMedia::selectedText( auto GroupedMedia::getBubbleSelectionIntervals( TextSelection selection) const --> std::vector { - auto result = std::vector(); +-> std::vector { + auto result = std::vector(); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; if (!IsGroupItemSelection(selection, i)) { @@ -528,7 +529,7 @@ auto GroupedMedia::getBubbleSelectionIntervals( const auto newHeight = std::max( last.top + last.height - newTop, geometry.top() + geometry.height() - newTop); - last = BubbleSelectionInterval{ newTop, newHeight }; + last = Ui::BubbleSelectionInterval{ newTop, newHeight }; } } const auto groupPadding = groupedPadding(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 442f9a5b1c..2d71c2c0da 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -57,7 +57,7 @@ public: TextForMimeData selectedText(TextSelection selection) const override; - std::vector getBubbleSelectionIntervals( + std::vector getBubbleSelectionIntervals( TextSelection selection) const override; void clickHandlerActiveChanged( diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 758c577223..1f64d7589c 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -709,8 +709,14 @@ object_ptr ForwardsPrivacyController::setupAboveWidget( Window::SectionWidget::PaintBackground(_controller, widget, rect); Painter p(widget); + const auto context = HistoryView::PaintContext{ + .bubblesPattern = nullptr, // #TODO bubbles + .viewport = widget->rect(), + .clip = widget->rect(), + .now = crl::now(), + }; p.translate(0, padding + view->marginBottom()); - view->draw(p, widget->rect(), TextSelection(), crl::now()); + view->draw(p, context); PaintForwardedTooltip(p, view, *option); }, widget->lifetime()); diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index 943c8e6cd7..513f098ec0 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -564,13 +564,18 @@ void ConfirmContactBox::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::boxBg); - const auto ms = crl::now(); + const auto context = HistoryView::PaintContext{ + .bubblesPattern = nullptr, // #TODO bubbles + .viewport = rect(), + .clip = rect(), + .now = crl::now(), + }; p.translate(st::boxPadding.left(), 0); if (_comment) { - _comment->draw(p, rect(), TextSelection(), ms); + _comment->draw(p, context); p.translate(0, _comment->height()); } - _contact->draw(p, rect(), TextSelection(), ms); + _contact->draw(p, context); } HistoryView::Context ConfirmContactBox::elementContext() { diff --git a/Telegram/SourceFiles/ui/chat/message_bubble.cpp b/Telegram/SourceFiles/ui/chat/message_bubble.cpp new file mode 100644 index 0000000000..ff7713eda0 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/message_bubble.cpp @@ -0,0 +1,368 @@ +/* +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 "ui/chat/message_bubble.h" + +#include "ui/cached_round_corners.h" +#include "ui/image/image_prepare.h" +#include "styles/style_chat.h" + +namespace Ui { +namespace { + +template < + typename FillBg, // fillBg(QRect rect) + typename FillSh, // fillSh(QRect rect) + typename FillRounded, // fillRounded(QRect rect, RectParts parts) + typename PaintTail> // paintTail(QPoint bottomPosition) -> tailWidth +void PaintBubbleGeneric( + const SimpleBubble &args, + FillBg &&fillBg, + FillSh &&fillSh, + FillRounded &&fillRounded, + PaintTail &&paintTail) { + auto parts = RectPart::None | RectPart::NoTopBottom; + auto rect = args.geometry; + if (args.skip & RectPart::Top) { + if (args.skip & RectPart::Bottom) { + fillBg(rect); + return; + } + rect.setTop(rect.y() - st::historyMessageRadius); + } else { + parts |= RectPart::FullTop; + } + const auto skipBottom = (args.skip & RectPart::Bottom); + if (skipBottom) { + rect.setHeight(rect.height() + st::historyMessageRadius); + } else { + parts |= RectPart::Bottom; + } + if (!skipBottom && args.tailSide == RectPart::Right) { + parts |= RectPart::BottomLeft; + fillBg({ + rect.x() + rect.width() - st::historyMessageRadius, + rect.y() + rect.height() - st::historyMessageRadius, + st::historyMessageRadius, + st::historyMessageRadius }); + const auto tailWidth = paintTail({ + rect.x() + rect.width(), + rect.y() + rect.height() }); + fillSh({ + rect.x() + rect.width() - st::historyMessageRadius, + rect.y() + rect.height(), + st::historyMessageRadius + tailWidth, + st::msgShadow }); + } else if (!skipBottom && args.tailSide == RectPart::Left) { + parts |= RectPart::BottomRight; + fillBg({ + rect.x(), + rect.y() + rect.height() - st::historyMessageRadius, + st::historyMessageRadius, + st::historyMessageRadius }); + const auto tailWidth = paintTail({ + rect.x(), + rect.y() + rect.height() }); + fillSh({ + rect.x() - tailWidth, + rect.y() + rect.height(), + st::historyMessageRadius + tailWidth, + st::msgShadow }); + } else if (!skipBottom) { + parts |= RectPart::FullBottom; + } + fillRounded(rect, parts); +} + +void PaintPatternBubble(Painter &p, const SimpleBubble &args) { + const auto pattern = args.pattern; + const auto sh = (args.skip & RectPart::Bottom) + ? nullptr + : &st::msgOutShadow; + const auto &tail = (args.tailSide == RectPart::Right) + ? pattern->tailRight + : pattern->tailLeft; + const auto tailShift = (args.tailSide == RectPart::Right + ? QPoint(0, tail.height()) + : QPoint(tail.width(), tail.height())) / int(tail.devicePixelRatio()); + const auto fillBg = [&](const QRect &rect) { + const auto fill = rect.intersected(args.patternViewport); + if (!fill.isEmpty()) { + p.setClipRect(fill); + // #TODO bubbles optimizes + const auto to = args.patternViewport; + const auto from = QRect(QPoint(), pattern->pixmap.size()); + p.drawPixmap(to, pattern->pixmap, from); + p.setClipping(false); + } + }; + const auto fillSh = [&](const QRect &rect) { + if (!(args.skip & RectPart::Bottom)) { + p.setOpacity(st::msgOutShadow->c.alphaF()); + fillBg(rect); + p.setOpacity(1.); + } + }; + const auto fillPattern = [&]( + int x, + int y, + const QImage &mask, + QImage &cache) { + Expects(mask.bytesPerLine() == mask.width() * 4); + Expects(mask.format() == QImage::Format_ARGB32_Premultiplied); + + if (cache.size() != mask.size()) { + cache = QImage( + mask.size(), + QImage::Format_ARGB32_Premultiplied); + } + cache.setDevicePixelRatio(mask.devicePixelRatio()); + Assert(cache.bytesPerLine() == cache.width() * 4); + memcpy(cache.bits(), mask.constBits(), mask.sizeInBytes()); + + auto q = QPainter(&cache); + q.setCompositionMode(QPainter::CompositionMode_SourceIn); + const auto to = args.patternViewport.translated(-x, -y); + const auto from = QRect(QPoint(), pattern->pixmap.size()); + q.drawPixmap(to, pattern->pixmap, from); + q.end(); + + p.drawImage(x, y, cache); + }; + const auto fillCorner = [&](int x, int y, int index) { + fillPattern( + x, + y, + pattern->corners[index], + (index < 2) ? pattern->cornerTopCache : pattern->cornerBottomCache); + }; + const auto fillRounded = [&](const QRect &rect, RectParts parts) { + const auto x = rect.x(); + const auto y = rect.y(); + const auto w = rect.width(); + const auto h = rect.height(); + + const auto cornerWidth = pattern->corners[0].width() + / style::DevicePixelRatio(); + const auto cornerHeight = pattern->corners[0].height() + / style::DevicePixelRatio(); + if (w < 2 * cornerWidth || h < 2 * cornerHeight) { + return; + } + if (w > 2 * cornerWidth) { + if (parts & RectPart::Top) { + fillBg({ + x + cornerWidth, + y, + w - 2 * cornerWidth, + cornerHeight }); + } + if (parts & RectPart::Bottom) { + fillBg({ + x + cornerWidth, + y + h - cornerHeight, + w - 2 * cornerWidth, + cornerHeight }); + if (sh) { + fillSh({ + x + cornerWidth, + y + h, + w - 2 * cornerWidth, + st::msgShadow }); + } + } + } + if (h > 2 * cornerHeight) { + if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) { + fillBg({ + x, + y + cornerHeight, + w, + h - 2 * cornerHeight }); + } else { + if (parts & RectPart::Left) { + fillBg({ + x, + y + cornerHeight, + cornerWidth, + h - 2 * cornerHeight }); + } + if ((parts & RectPart::Center) && w > 2 * cornerWidth) { + fillBg({ + x + cornerWidth, + y + cornerHeight, + w - 2 * cornerWidth, + h - 2 * cornerHeight }); + } + if (parts & RectPart::Right) { + fillBg({ + x + w - cornerWidth, + y + cornerHeight, + cornerWidth, + h - 2 * cornerHeight }); + } + } + } + if (parts & RectPart::TopLeft) { + fillCorner(x, y, 0); + } + if (parts & RectPart::TopRight) { + fillCorner(x + w - cornerWidth, y, 1); + } + if (parts & RectPart::BottomLeft) { + fillCorner(x, y + h - cornerHeight, 2); + } + if (parts & RectPart::BottomRight) { + fillCorner(x + w - cornerWidth, y + h - cornerHeight, 3); + } + }; + const auto paintTail = [&](QPoint bottomPosition) { + const auto position = bottomPosition - tailShift; + fillPattern(position.x(), position.y(), tail, pattern->tailCache); + return tail.width() / int(tail.devicePixelRatio()); + }; + PaintBubbleGeneric(args, fillBg, fillSh, fillRounded, paintTail); +} + +void PaintSolidBubble(Painter &p, const SimpleBubble &args) { + const auto &bg = args.selected + ? (args.outbg ? st::msgOutBgSelected : st::msgInBgSelected) + : (args.outbg ? st::msgOutBg : st::msgInBg); + const auto sh = (args.skip & RectPart::Bottom) + ? nullptr + : args.selected + ? &(args.outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) + : &(args.outbg ? st::msgOutShadow : st::msgInShadow); + const auto corners = args.selected + ? (args.outbg + ? MessageOutSelectedCorners + : MessageInSelectedCorners) + : (args.outbg ? MessageOutCorners : MessageInCorners); + const auto &tail = (args.tailSide == RectPart::Right) + ? (args.selected + ? st::historyBubbleTailOutRightSelected + : st::historyBubbleTailOutRight) + : args.selected + ? (args.outbg + ? st::historyBubbleTailOutLeftSelected + : st::historyBubbleTailInLeftSelected) + : (args.outbg + ? st::historyBubbleTailOutLeft + : st::historyBubbleTailInLeft); + const auto tailShift = (args.tailSide == RectPart::Right) + ? QPoint(0, tail.height()) + : QPoint(tail.width(), tail.height()); + PaintBubbleGeneric(args, [&](const QRect &rect) { + p.fillRect(rect, bg); + }, [&](const QRect &rect) { + p.fillRect(rect, *sh); + }, [&](const QRect &rect, RectParts parts) { + Ui::FillRoundRect(p, rect, bg, corners, sh, parts); + }, [&](const QPoint &bottomPosition) { + tail.paint(p, bottomPosition - tailShift, args.outerWidth); + return tail.width(); + }); +} + +} // namespace + +std::unique_ptr PrepareBubblePattern() { + auto result = std::make_unique(); + result->corners = Images::CornersMask(st::historyMessageRadius); + const auto addShadow = [&](QImage &bottomCorner) { + auto result = QImage( + bottomCorner.width(), + (bottomCorner.height() + + st::msgShadow * int(bottomCorner.devicePixelRatio())), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(bottomCorner.devicePixelRatio()); + auto p = QPainter(&result); + p.setOpacity(st::msgInShadow->c.alphaF()); + p.drawImage(0, st::msgShadow, bottomCorner); + p.setOpacity(1.); + p.drawImage(0, 0, bottomCorner); + p.end(); + + bottomCorner = std::move(result); + }; + addShadow(result->corners[2]); + addShadow(result->corners[3]); + result->tailLeft = st::historyBubbleTailOutLeft.instance(Qt::white); + result->tailRight = st::historyBubbleTailOutRight.instance(Qt::white); + result->tailCache = QImage( + result->tailLeft.size(), + QImage::Format_ARGB32_Premultiplied); + result->cornerTopCache = QImage( + result->corners[0].size(), + QImage::Format_ARGB32_Premultiplied); + result->cornerBottomCache = QImage( + result->corners[2].size(), + QImage::Format_ARGB32_Premultiplied); + return result; +} + +void PaintBubble(Painter &p, const SimpleBubble &args) { + if (!args.selected + && args.outbg + && args.pattern + && !args.patternViewport.isEmpty() + && !args.pattern->pixmap.size().isEmpty()) { + PaintPatternBubble(p, args); + } else { + PaintSolidBubble(p, args); + } +} + +void PaintBubble(Painter &p, const ComplexBubble &args) { + if (args.selection.empty()) { + PaintBubble(p, args.simple); + return; + } + const auto rect = args.simple.geometry; + const auto left = rect.x(); + const auto width = rect.width(); + const auto top = rect.y(); + const auto bottom = top + rect.height(); + const auto paintOne = [&](QRect geometry, bool selected, RectParts skip) { + auto simple = args.simple; + simple.geometry = geometry; + simple.selected = selected; + simple.skip = skip; + PaintBubble(p, simple); + }; + auto from = top; + for (const auto &selected : args.selection) { + if (selected.top > from) { + const auto skip = RectPart::Bottom + | (from > top ? RectPart::Top : RectPart::None); + paintOne( + QRect(left, from, width, selected.top - from), + false, + skip); + } + const auto skip = ((selected.top > top) + ? RectPart::Top + : RectPart::None) + | ((selected.top + selected.height < bottom) + ? RectPart::Bottom + : RectPart::None); + paintOne( + QRect(left, selected.top, width, selected.height), + true, + skip); + from = selected.top + selected.height; + } + if (from < bottom) { + paintOne( + QRect(left, from, width, bottom - from), + false, + RectPart::Top); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/message_bubble.h b/Telegram/SourceFiles/ui/chat/message_bubble.h new file mode 100644 index 0000000000..f6bacaf071 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/message_bubble.h @@ -0,0 +1,52 @@ +/* +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 "ui/rect_part.h" + +class Painter; + +namespace Ui { + +struct BubbleSelectionInterval { + int top = 0; + int height = 0; +}; + +struct BubblePattern { + QPixmap pixmap; + std::array corners; + QImage tailLeft; + QImage tailRight; + mutable QImage cornerTopCache; + mutable QImage cornerBottomCache; + mutable QImage tailCache; +}; + +[[nodiscard]] std::unique_ptr PrepareBubblePattern(); + +struct SimpleBubble { + QRect geometry; + const BubblePattern *pattern = nullptr; + QRect patternViewport; + int outerWidth = 0; + bool selected = false; + bool outbg = false; + RectPart tailSide = RectPart::None; + RectParts skip = RectPart::None; +}; + +struct ComplexBubble { + SimpleBubble simple; + const std::vector &selection; +}; + +void PaintBubble(Painter &p, const SimpleBubble &args); +void PaintBubble(Painter &p, const ComplexBubble &args); + +} // namespace Ui diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 7fc2284535..6b5d7b29c9 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -106,13 +106,12 @@ void SectionWidget::PaintBackground( Painter p(widget); const auto background = Window::Theme::Background(); - const auto fullHeight = controller->content()->height(); if (const auto color = background->colorForFill()) { p.fillRect(clip, *color); return; } const auto gradient = background->gradientForFill(); - const auto fill = QSize(widget->width(), fullHeight); + const auto fill = QSize(widget->width(), controller->content()->height()); auto fromy = controller->content()->backgroundFromY(); auto state = controller->backgroundState(fill); const auto paintCache = [&](const CachedBackground &cache) { @@ -150,7 +149,26 @@ void SectionWidget::PaintBackground( return; } const auto &prepared = background->prepared(); - if (!prepared.isNull() && !background->tile()) { + if (prepared.isNull()) { + return; + } else if (background->tile()) { + const auto &tiled = background->preparedForTiled(); + const auto left = clip.left(); + const auto top = clip.top(); + const auto right = clip.left() + clip.width(); + const auto bottom = clip.top() + clip.height(); + const auto w = tiled.width() / cRetinaFactor(); + const auto h = tiled.height() / cRetinaFactor(); + const auto sx = qFloor(left / w); + const auto sy = qFloor((top - fromy) / h); + const auto cx = qCeil(right / w); + const auto cy = qCeil((bottom - fromy) / h); + for (auto i = sx; i < cx; ++i) { + for (auto j = sy; j < cy; ++j) { + p.drawImage(QPointF(i * w, fromy + j * h), tiled); + } + } + } else { const auto hq = PainterHighQualityEnabler(p); const auto rects = Window::Theme::ComputeBackgroundRects( fill, @@ -158,25 +176,6 @@ void SectionWidget::PaintBackground( auto to = rects.to; to.moveTop(to.top() + fromy); p.drawImage(to, prepared, rects.from); - return; - } - if (!prepared.isNull()) { - const auto &tiled = background->preparedForTiled(); - auto left = clip.left(); - auto top = clip.top(); - auto right = clip.left() + clip.width(); - auto bottom = clip.top() + clip.height(); - auto w = tiled.width() / cRetinaFactor(); - auto h = tiled.height() / cRetinaFactor(); - auto sx = qFloor(left / w); - auto sy = qFloor((top - fromy) / h); - auto cx = qCeil(right / w); - auto cy = qCeil((bottom - fromy) / h); - for (auto i = sx; i < cx; ++i) { - for (auto j = sy; j < cy; ++j) { - p.drawImage(QPointF(i * w, fromy + j * h), tiled); - } - } } } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 678350a5a9..a56ddd0fd4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/delayed_activation.h" +#include "ui/chat/message_bubble.h" #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" #include "calls/calls_instance.h" // Core::App().calls().inCall(). @@ -63,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "styles/style_dialogs.h" #include "styles/style_layers.h" // st::boxLabel +#include "styles/style_chat.h" // st::historyMessageRadius #include @@ -1445,6 +1447,49 @@ void SessionController::openDocument( session().data().message(contextId)); } +void SessionController::setBubblesBackground(QImage image) { + _bubblesBackgroundPrepared = std::move(image); + if (!_bubblesBackground.area.isEmpty()) { + _bubblesBackground = CacheBackground({ + .prepared = _bubblesBackgroundPrepared, + .area = _bubblesBackground.area, + }); + } + if (!_bubblesBackgroundPattern) { + _bubblesBackgroundPattern = Ui::PrepareBubblePattern(); + } + _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; + _repaintBackgroundRequests.fire({}); +} + +HistoryView::PaintContext SessionController::bubblesContext( + BubblesContextArgs &&args) { + const auto visibleAreaTopLocal = content()->mapFromGlobal( + QPoint(0, args.visibleAreaTopGlobal)).y(); + const auto viewport = QRect( + 0, + args.visibleAreaTop - visibleAreaTopLocal, + args.visibleAreaWidth, + content()->height()); + _bubblesBackground.area = viewport.size(); + //if (!_bubblesBackgroundPrepared.isNull() + // && _bubblesBackground.area != viewport.size() + // && !viewport.isEmpty()) { + // // #TODO bubbles delayed caching + // _bubblesBackground = CacheBackground({ + // .prepared = _bubblesBackgroundPrepared, + // .area = viewport.size(), + // }); + // _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; + //} + return { + .bubblesPattern = _bubblesBackgroundPattern.get(), + .viewport = viewport.translated(0, -args.initialShift), + .clip = args.clip.translated(0, -args.initialShift), + .now = crl::now(), + }; +} + const BackgroundState &SessionController::backgroundState(QSize area) { _backgroundState.shown = _backgroundFade.value(1.); if (_backgroundState.now.pixmap.isNull() @@ -1580,7 +1625,8 @@ void SessionController::setCachedBackground(CacheBackgroundResult &&cached) { const auto background = Window::Theme::Background(); if (background->gradientForFill().isNull() - || _backgroundState.now.pixmap.isNull()) { + || _backgroundState.now.pixmap.isNull() + || anim::Disabled()) { _backgroundFade.stop(); _backgroundState.shown = 1.; _backgroundState.now = std::move(cached); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index a30f7cb60e..eee38e5113 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -26,6 +26,10 @@ namespace Adaptive { enum class WindowLayout; } // namespace Adaptive +namespace HistoryView { +struct PaintContext; +} // namespace HistoryView + namespace ChatHelpers { class TabbedSelector; } // namespace ChatHelpers @@ -46,6 +50,7 @@ class FormController; namespace Ui { class LayerWidget; enum class ReportReason; +struct BubblePattern; } // namespace Ui namespace Window { @@ -439,6 +444,20 @@ public: void toggleFiltersMenu(bool enabled); [[nodiscard]] rpl::producer<> filtersMenuChanged() const; + void setBubblesBackground(QImage image); + const Ui::BubblePattern *bubblesBackgroundPattern() const { + return _bubblesBackgroundPattern.get(); + } + + struct BubblesContextArgs { + int visibleAreaTop = 0; + int visibleAreaTopGlobal = 0; + int visibleAreaWidth = 0; + QRect clip; + int initialShift = 0; + }; + [[nodiscard]] HistoryView::PaintContext bubblesContext( + BubblesContextArgs &&args); [[nodiscard]] const BackgroundState &backgroundState(QSize area); [[nodiscard]] rpl::producer<> repaintBackgroundRequests() const; void rotateComplexGradientBackground(); @@ -520,6 +539,10 @@ private: QSize _willCacheForArea; crl::time _lastAreaChangeTime = 0; base::Timer _cacheBackgroundTimer; + CachedBackground _bubblesBackground; + QImage _bubblesBackgroundPrepared; + std::unique_ptr _bubblesBackgroundPattern; + rpl::event_stream<> _repaintBackgroundRequests; rpl::lifetime _lifetime; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index c886f61a9b..c955d53f45 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -142,6 +142,8 @@ PRIVATE ui/chat/group_call_userpics.h ui/chat/message_bar.cpp ui/chat/message_bar.h + ui/chat/message_bubble.cpp + ui/chat/message_bubble.h ui/chat/pinned_bar.cpp ui/chat/pinned_bar.h ui/controls/call_mute_button.cpp