mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Allow arbitrary QImage as outgoing bubbles background.
This commit is contained in:
parent
1cc9a52461
commit
f4fdadd3b0
21 changed files with 677 additions and 232 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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() {
|
||||
|
|
368
Telegram/SourceFiles/ui/chat/message_bubble.cpp
Normal file
368
Telegram/SourceFiles/ui/chat/message_bubble.cpp
Normal 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
|
52
Telegram/SourceFiles/ui/chat/message_bubble.h
Normal file
52
Telegram/SourceFiles/ui/chat/message_bubble.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue