Allow arbitrary QImage as outgoing bubbles background.

This commit is contained in:
John Preston 2021-08-19 17:22:12 +03:00
parent 1cc9a52461
commit f4fdadd3b0
21 changed files with 677 additions and 232 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -24,6 +24,7 @@ class SessionController;
namespace Ui {
class PathShiftGradient;
struct BubblePattern;
} // namespace Ui
namespace HistoryView {
@ -191,6 +192,14 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct PaintContext {
const Ui::BubblePattern *bubblesPattern = nullptr;
QRect viewport;
QRect clip;
TextSelection selection;
crl::time now = 0;
};
class Element
: public Object
, public RuntimeComposer<Element>
@ -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,

View File

@ -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);

View File

@ -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<BubbleSelectionInterval> &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<DateBadge>()) {
@ -568,7 +468,7 @@ void Message::draw(
}
if (const auto bar = Get<UnreadBar>()) {
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<BubbleSelectionInterval>();
? media->getBubbleSelectionIntervals(context.selection)
: std::vector<Ui::BubbleSelectionInterval>();
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());
}

View File

@ -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,

View File

@ -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<DateBadge>()) {
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();

View File

@ -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(

View File

@ -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<Ui::BubbleSelectionInterval> {
return {};
}
PointState Media::pointState(QPoint point) const {
return QRect(0, 0, width(), height()).contains(point)
? PointState::Inside

View File

@ -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<DocumentData*> document,
FullMsgId context);
@ -125,9 +124,7 @@ public:
[[nodiscard]] virtual auto getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<BubbleSelectionInterval> {
return {};
}
-> std::vector<Ui::BubbleSelectionInterval>;
// if we press and drag this link should we drag the item
[[nodiscard]] virtual bool dragItemByHandler(

View File

@ -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<BubbleSelectionInterval> {
auto result = std::vector<BubbleSelectionInterval>();
-> std::vector<Ui::BubbleSelectionInterval> {
auto result = std::vector<Ui::BubbleSelectionInterval>();
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();

View File

@ -57,7 +57,7 @@ public:
TextForMimeData selectedText(TextSelection selection) const override;
std::vector<BubbleSelectionInterval> getBubbleSelectionIntervals(
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
TextSelection selection) const override;
void clickHandlerActiveChanged(

View File

@ -709,8 +709,14 @@ object_ptr<Ui::RpWidget> 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());

View File

@ -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() {

View File

@ -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<BubblePattern> PrepareBubblePattern() {
auto result = std::make_unique<Ui::BubblePattern>();
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

View File

@ -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<QImage, 4> corners;
QImage tailLeft;
QImage tailRight;
mutable QImage cornerTopCache;
mutable QImage cornerBottomCache;
mutable QImage tailCache;
};
[[nodiscard]] std::unique_ptr<BubblePattern> 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<BubbleSelectionInterval> &selection;
};
void PaintBubble(Painter &p, const SimpleBubble &args);
void PaintBubble(Painter &p, const ComplexBubble &args);
} // namespace Ui

View File

@ -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);
}
}
}
}

View File

@ -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 <QtGui/QGuiApplication>
@ -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);

View File

@ -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<Ui::BubblePattern> _bubblesBackgroundPattern;
rpl::event_stream<> _repaintBackgroundRequests;
rpl::lifetime _lifetime;

View File

@ -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