tdesktop/Telegram/SourceFiles/ui/chat/message_bubble.cpp

461 lines
12 KiB
C++

/*
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 "ui/chat/chat_style.h"
#include "styles/style_chat.h"
namespace Ui {
namespace {
using Corner = BubbleCornerRounding;
template <
typename FillBg, // fillBg(QRect rect)
typename FillSh, // fillSh(QRect rect)
typename FillCorner, // fillCorner(int x, int y, int index, Corner size)
typename PaintTail> // paintTail(QPoint bottomPosition) -> tailWidth
void PaintBubbleGeneric(
const SimpleBubble &args,
FillBg &&fillBg,
FillSh &&fillSh,
FillCorner &&fillCorner,
PaintTail &&paintTail) {
using namespace Images;
const auto topLeft = args.rounding.topLeft;
const auto topRight = args.rounding.topRight;
const auto bottomWithTailLeft = args.rounding.bottomLeft;
const auto bottomWithTailRight = args.rounding.bottomRight;
if (topLeft == Corner::None
&& topRight == Corner::None
&& bottomWithTailLeft == Corner::None
&& bottomWithTailRight == Corner::None) {
fillBg(args.geometry);
return;
}
const auto bottomLeft = (bottomWithTailLeft == Corner::Tail)
? Corner::None
: bottomWithTailLeft;
const auto bottomRight = (bottomWithTailRight == Corner::Tail)
? Corner::None
: bottomWithTailRight;
const auto rect = args.geometry;
const auto small = BubbleRadiusSmall();
const auto large = BubbleRadiusLarge();
const auto cornerSize = [&](Corner corner) {
return (corner == Corner::Large)
? large
: (corner == Corner::Small)
? small
: 0;
};
const auto verticalSkip = [&](Corner left, Corner right) {
return std::max(cornerSize(left), cornerSize(right));
};
const auto top = verticalSkip(topLeft, topRight);
const auto bottom = verticalSkip(bottomLeft, bottomRight);
if (top) {
const auto left = cornerSize(topLeft);
const auto right = cornerSize(topRight);
if (left) {
fillCorner(rect.left(), rect.top(), kTopLeft, topLeft);
if (const auto add = top - left) {
fillBg({ rect.left(), rect.top() + left, left, add });
}
}
if (const auto fill = rect.width() - left - right; fill > 0) {
fillBg({ rect.left() + left, rect.top(), fill, top });
}
if (right) {
fillCorner(
rect.left() + rect.width() - right,
rect.top(),
kTopRight,
topRight);
if (const auto add = top - right) {
fillBg({
rect.left() + rect.width() - right,
rect.top() + right,
right,
add,
});
}
}
}
if (const auto fill = rect.height() - top - bottom; fill > 0) {
fillBg({ rect.left(), rect.top() + top, rect.width(), fill });
}
if (bottom) {
const auto left = cornerSize(bottomLeft);
const auto right = cornerSize(bottomRight);
if (left) {
fillCorner(
rect.left(),
rect.top() + rect.height() - left,
kBottomLeft,
bottomLeft);
if (const auto add = bottom - left) {
fillBg({
rect.left(),
rect.top() + rect.height() - bottom,
left,
add,
});
}
}
if (const auto fill = rect.width() - left - right; fill > 0) {
fillBg({
rect.left() + left,
rect.top() + rect.height() - bottom,
fill,
bottom,
});
}
if (right) {
fillCorner(
rect.left() + rect.width() - right,
rect.top() + rect.height() - right,
kBottomRight,
bottomRight);
if (const auto add = bottom - right) {
fillBg({
rect.left() + rect.width() - right,
rect.top() + rect.height() - bottom,
right,
add,
});
}
}
}
const auto leftTail = (bottomWithTailLeft == Corner::Tail)
? paintTail({ rect.x(), rect.y() + rect.height() })
: 0;
const auto rightTail = (bottomWithTailRight == Corner::Tail)
? paintTail({ rect.x() + rect.width(), rect.y() + rect.height() })
: 0;
if (!args.shadowed) {
return;
}
const auto shLeft = rect.x() + cornerSize(bottomLeft) - leftTail;
const auto shWidth = rect.x()
+ rect.width()
- cornerSize(bottomRight)
+ rightTail
- shLeft;
if (shWidth > 0) {
fillSh({ shLeft, rect.y() + rect.height(), shWidth, st::msgShadow });
}
}
void PaintPatternBubble(QPainter &p, const SimpleBubble &args) {
const auto opacity = args.st->msgOutBg()->c.alphaF();
const auto shadowOpacity = opacity * args.st->msgOutShadow()->c.alphaF();
const auto pattern = args.pattern;
const auto &tail = (args.rounding.bottomRight == Corner::Tail)
? pattern->tailRight
: pattern->tailLeft;
const auto tailShift = (args.rounding.bottomRight == Corner::Tail
? 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()) {
PaintPatternBubblePart(
p,
args.patternViewport,
pattern->pixmap,
fill);
}
};
const auto fillSh = [&](const QRect &rect) {
p.setOpacity(shadowOpacity);
fillBg(rect);
p.setOpacity(opacity);
};
const auto fillPattern = [&](
int x,
int y,
const QImage &mask,
QImage &cache) {
PaintPatternBubblePart(
p,
args.patternViewport,
pattern->pixmap,
QRect(QPoint(x, y), mask.size() / int(mask.devicePixelRatio())),
mask,
cache);
};
const auto fillCorner = [&](int x, int y, int index, Corner size) {
auto &corner = (size == Corner::Large)
? pattern->cornersLarge[index]
: pattern->cornersSmall[index];
auto &cache = (size == Corner::Large)
? (index < 2
? pattern->cornerTopLargeCache
: pattern->cornerBottomLargeCache)
: (index < 2
? pattern->cornerTopSmallCache
: pattern->cornerBottomSmallCache);
fillPattern(x, y, corner, cache);
};
const auto paintTail = [&](QPoint bottomPosition) {
const auto position = bottomPosition - tailShift;
fillPattern(position.x(), position.y(), tail, pattern->tailCache);
return tail.width() / int(tail.devicePixelRatio());
};
p.setOpacity(opacity);
PaintBubbleGeneric(args, fillBg, fillSh, fillCorner, paintTail);
p.setOpacity(1.);
}
void PaintSolidBubble(QPainter &p, const SimpleBubble &args) {
const auto &st = args.st->messageStyle(args.outbg, args.selected);
const auto &bg = st.msgBg;
const auto sh = (args.rounding.bottomRight == Corner::None)
? nullptr
: &st.msgShadow;
const auto &tail = (args.rounding.bottomRight == Corner::Tail)
? st.tailRight
: st.tailLeft;
const auto tailShift = (args.rounding.bottomRight == Corner::Tail)
? 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);
}, [&](int x, int y, int index, Corner size) {
auto &corners = (size == Corner::Large)
? st.msgBgCornersLarge
: st.msgBgCornersSmall;
p.drawPixmap(x, y, corners.p[index]);
}, [&](const QPoint &bottomPosition) {
tail.paint(p, bottomPosition - tailShift, args.outerWidth);
return tail.width();
});
}
} // namespace
std::unique_ptr<BubblePattern> PrepareBubblePattern(
not_null<const style::palette*> st) {
auto result = std::make_unique<Ui::BubblePattern>();
result->cornersSmall = Images::CornersMask(BubbleRadiusSmall());
result->cornersLarge = Images::CornersMask(BubbleRadiusLarge());
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->cornersSmall[2]);
addShadow(result->cornersSmall[3]);
result->cornerTopSmallCache = QImage(
result->cornersSmall[0].size(),
QImage::Format_ARGB32_Premultiplied);
result->cornerTopLargeCache = QImage(
result->cornersLarge[0].size(),
QImage::Format_ARGB32_Premultiplied);
result->cornerBottomSmallCache = QImage(
result->cornersSmall[2].size(),
QImage::Format_ARGB32_Premultiplied);
result->cornerBottomLargeCache = QImage(
result->cornersLarge[2].size(),
QImage::Format_ARGB32_Premultiplied);
return result;
}
void FinishBubblePatternOnMain(not_null<BubblePattern*> pattern) {
pattern->tailLeft = st::historyBubbleTailOutLeft.instance(Qt::white);
pattern->tailRight = st::historyBubbleTailOutRight.instance(Qt::white);
pattern->tailCache = QImage(
pattern->tailLeft.size(),
QImage::Format_ARGB32_Premultiplied);
}
void PaintBubble(QPainter &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(QPainter &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,
bool fromTop,
bool tillBottom) {
auto simple = args.simple;
simple.geometry = geometry;
simple.selected = selected;
if (!fromTop) {
simple.rounding.topLeft
= simple.rounding.topRight
= Corner::None;
}
if (!tillBottom) {
simple.rounding.bottomLeft
= simple.rounding.bottomRight
= Corner::None;
simple.shadowed = false;
}
PaintBubble(p, simple);
};
auto from = top;
for (const auto &selected : args.selection) {
if (selected.top > from) {
paintOne(
QRect(left, from, width, selected.top - from),
false,
(from <= top),
false);
}
paintOne(
QRect(left, selected.top, width, selected.height),
true,
(selected.top <= top),
(selected.top + selected.height >= bottom));
from = selected.top + selected.height;
}
if (from < bottom) {
paintOne(
QRect(left, from, width, bottom - from),
false,
false,
true);
}
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target) {
const auto factor = pixmap.devicePixelRatio();
if (viewport.size() * factor == pixmap.size()) {
const auto fill = target.intersected(viewport);
if (fill.isEmpty()) {
return;
}
p.drawPixmap(fill, pixmap, QRect(
(fill.topLeft() - viewport.topLeft()) * factor,
fill.size() * factor));
} else {
const auto to = viewport;
const auto from = QRect(QPoint(), pixmap.size());
const auto deviceRect = QRect(
QPoint(),
QSize(p.device()->width(), p.device()->height()));
const auto clip = (target != deviceRect);
if (clip) {
p.setClipRect(target);
}
p.drawPixmap(to, pixmap, from);
if (clip) {
p.setClipping(false);
}
}
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
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);
PaintPatternBubblePart(
q,
viewport.translated(-target.topLeft()),
pixmap,
QRect(QPoint(), cache.size() / int(cache.devicePixelRatio())));
q.end();
p.drawImage(target, cache);
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
Fn<void(QPainter&)> paintContent,
QImage &cache) {
Expects(paintContent != nullptr);
const auto targetOrigin = target.topLeft();
const auto targetSize = target.size();
if (cache.size() != targetSize * style::DevicePixelRatio()) {
cache = QImage(
target.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(style::DevicePixelRatio());
}
cache.fill(Qt::transparent);
auto q = QPainter(&cache);
q.translate(-targetOrigin);
paintContent(q);
q.translate(targetOrigin);
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
PaintPatternBubblePart(
q,
viewport.translated(-targetOrigin),
pixmap,
QRect(QPoint(), targetSize));
q.end();
p.drawImage(target, cache);
}
} // namespace Ui