| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- /*
- 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
|