2017-09-04 13:40:02 +02:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 11:23:14 +01:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-09-04 13:40:02 +02:00
|
|
|
|
2018-01-03 11:23:14 +01:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-09-04 13:40:02 +02:00
|
|
|
*/
|
2019-08-02 15:21:09 +02:00
|
|
|
#include "history/view/media/history_view_media.h"
|
2017-09-04 13:40:02 +02:00
|
|
|
|
2020-06-24 10:22:27 +02:00
|
|
|
#include "history/history.h"
|
2018-01-13 13:45:11 +01:00
|
|
|
#include "history/history_item.h"
|
2018-01-14 17:02:25 +01:00
|
|
|
#include "history/view/history_view_element.h"
|
2018-01-27 14:59:24 +01:00
|
|
|
#include "history/view/history_view_cursor_state.h"
|
2021-12-24 11:01:58 +01:00
|
|
|
#include "history/view/history_view_spoiler_click_handler.h"
|
2022-08-04 12:35:08 +02:00
|
|
|
#include "history/view/media/history_view_sticker.h"
|
2022-12-12 19:57:17 +01:00
|
|
|
#include "history/view/media/history_view_media_spoiler.h"
|
2017-09-04 13:40:02 +02:00
|
|
|
#include "storage/storage_shared_media.h"
|
2019-12-25 16:20:02 +01:00
|
|
|
#include "data/data_document.h"
|
2022-01-11 15:13:04 +01:00
|
|
|
#include "data/data_session.h"
|
2021-11-18 13:03:12 +01:00
|
|
|
#include "data/data_web_page.h"
|
2020-10-10 11:15:37 +02:00
|
|
|
#include "ui/item_text_options.h"
|
2021-09-03 00:08:57 +02:00
|
|
|
#include "ui/chat/chat_style.h"
|
2021-08-19 16:22:12 +02:00
|
|
|
#include "ui/chat/message_bubble.h"
|
2023-10-13 08:04:29 +02:00
|
|
|
#include "ui/effects/spoiler_mess.h"
|
2022-09-30 21:03:44 +02:00
|
|
|
#include "ui/image/image_prepare.h"
|
2023-02-21 13:31:55 +01:00
|
|
|
#include "ui/power_saving.h"
|
2020-06-24 10:22:27 +02:00
|
|
|
#include "core/ui_integration.h"
|
2020-10-10 11:15:37 +02:00
|
|
|
#include "styles/style_chat.h"
|
2017-09-04 13:40:02 +02:00
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
namespace HistoryView {
|
2019-12-25 16:20:02 +01:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
[[nodiscard]] TimeId TimeFromMatch(
|
2021-10-19 15:00:21 +02:00
|
|
|
QStringView hours,
|
|
|
|
QStringView minutes1,
|
|
|
|
QStringView minutes2,
|
|
|
|
QStringView seconds) {
|
2019-12-25 16:20:02 +01:00
|
|
|
auto ok1 = true;
|
|
|
|
auto ok2 = true;
|
|
|
|
auto ok3 = true;
|
2019-12-31 15:52:15 +01:00
|
|
|
auto minutes = minutes1.toString();
|
|
|
|
minutes += minutes2;
|
2020-01-16 09:28:03 +01:00
|
|
|
const auto value1 = (hours.isEmpty() ? 0 : hours.toInt(&ok1));
|
|
|
|
const auto value2 = minutes.toInt(&ok2);
|
|
|
|
const auto value3 = seconds.toInt(&ok3);
|
|
|
|
const auto ok = ok1 && ok2 && ok3;
|
|
|
|
return (ok && value3 < 60 && (hours.isEmpty() || value2 < 60))
|
|
|
|
? (value1 * 3600 + value2 * 60 + value3)
|
|
|
|
: -1;
|
2019-12-25 16:20:02 +01:00
|
|
|
}
|
|
|
|
|
2019-12-27 11:41:30 +01:00
|
|
|
} // namespace
|
|
|
|
|
2021-11-18 13:03:12 +01:00
|
|
|
TimeId DurationForTimestampLinks(not_null<DocumentData*> document) {
|
|
|
|
if (!document->isVideoFile()
|
|
|
|
&& !document->isSong()
|
|
|
|
&& !document->isVoiceMessage()) {
|
|
|
|
return TimeId(0);
|
|
|
|
}
|
2023-06-05 14:50:43 +02:00
|
|
|
return std::max(document->duration(), crl::time(0)) / 1000;
|
2021-11-18 13:03:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QString TimestampLinkBase(
|
2019-12-27 11:41:30 +01:00
|
|
|
not_null<DocumentData*> document,
|
|
|
|
FullMsgId context) {
|
|
|
|
return QString(
|
2021-11-18 13:03:12 +01:00
|
|
|
"media_timestamp?base=doc%1_%2_%3&t="
|
2021-12-09 08:32:54 +01:00
|
|
|
).arg(document->id).arg(context.peer.value).arg(context.msg.bare);
|
2019-12-27 11:41:30 +01:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:03:12 +01:00
|
|
|
TimeId DurationForTimestampLinks(not_null<WebPageData*> webpage) {
|
|
|
|
if (!webpage->collage.items.empty()) {
|
2021-12-02 11:42:33 +01:00
|
|
|
return 0;
|
2021-11-18 13:03:12 +01:00
|
|
|
} else if (const auto document = webpage->document) {
|
|
|
|
return DurationForTimestampLinks(document);
|
|
|
|
} else if (webpage->type != WebPageType::Video
|
2022-11-26 22:20:17 +01:00
|
|
|
|| webpage->siteName != u"YouTube"_q) {
|
2021-11-18 13:03:12 +01:00
|
|
|
return TimeId(0);
|
|
|
|
} else if (webpage->duration > 0) {
|
|
|
|
return webpage->duration;
|
|
|
|
}
|
2022-03-16 16:16:17 +01:00
|
|
|
constexpr auto kMaxYouTubeTimestampDuration = 100 * 60 * TimeId(60);
|
2021-11-18 13:03:12 +01:00
|
|
|
return kMaxYouTubeTimestampDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TimestampLinkBase(
|
|
|
|
not_null<WebPageData*> webpage,
|
|
|
|
FullMsgId context) {
|
|
|
|
const auto url = webpage->url;
|
|
|
|
if (url.isEmpty()) {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
auto parts = url.split(QChar('#'));
|
|
|
|
const auto base = parts[0];
|
|
|
|
parts.pop_front();
|
|
|
|
const auto use = [&] {
|
|
|
|
const auto query = base.indexOf(QChar('?'));
|
|
|
|
if (query < 0) {
|
|
|
|
return base + QChar('?');
|
|
|
|
}
|
|
|
|
auto params = base.mid(query + 1).split(QChar('&'));
|
|
|
|
for (auto i = params.begin(); i != params.end();) {
|
|
|
|
if (i->startsWith("t=")) {
|
|
|
|
i = params.erase(i);
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return base.mid(0, query)
|
|
|
|
+ (params.empty() ? "?" : ("?" + params.join(QChar('&')) + "&"));
|
|
|
|
}();
|
|
|
|
return "url:"
|
|
|
|
+ use
|
|
|
|
+ "t="
|
|
|
|
+ (parts.empty() ? QString() : ("#" + parts.join(QChar('#'))));
|
|
|
|
}
|
|
|
|
|
2019-12-27 11:41:30 +01:00
|
|
|
TextWithEntities AddTimestampLinks(
|
2019-12-25 16:20:02 +01:00
|
|
|
TextWithEntities text,
|
|
|
|
TimeId duration,
|
|
|
|
const QString &base) {
|
2021-11-18 13:03:12 +01:00
|
|
|
if (base.isEmpty()) {
|
|
|
|
return text;
|
|
|
|
}
|
2019-12-25 16:20:02 +01:00
|
|
|
static const auto expression = QRegularExpression(
|
2023-01-30 11:10:42 +01:00
|
|
|
"(?<![^\\s\\(\\)\"\\,\\.\\-])"
|
|
|
|
"(?:(?:(\\d{1,2}):)?(\\d))?(\\d):(\\d\\d)"
|
|
|
|
"(?![^\\s\\(\\)\",\\.\\-\\+])");
|
2019-12-25 16:20:02 +01:00
|
|
|
const auto &string = text.text;
|
|
|
|
auto offset = 0;
|
|
|
|
while (true) {
|
|
|
|
const auto m = expression.match(string, offset);
|
|
|
|
if (!m.hasMatch()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto from = m.capturedStart();
|
|
|
|
const auto till = from + m.capturedLength();
|
|
|
|
offset = till;
|
|
|
|
|
|
|
|
const auto time = TimeFromMatch(
|
2021-10-19 15:00:21 +02:00
|
|
|
m.capturedView(1),
|
|
|
|
m.capturedView(2),
|
|
|
|
m.capturedView(3),
|
|
|
|
m.capturedView(4));
|
2019-12-25 16:20:02 +01:00
|
|
|
if (time < 0 || time > duration) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto &entities = text.entities;
|
|
|
|
const auto i = ranges::lower_bound(
|
|
|
|
entities,
|
|
|
|
from,
|
|
|
|
std::less<>(),
|
|
|
|
&EntityInText::offset);
|
|
|
|
if (i != entities.end() && i->offset() < till) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto intersects = [&](const EntityInText &entity) {
|
|
|
|
return entity.offset() + entity.length() > from;
|
|
|
|
};
|
|
|
|
auto j = std::make_reverse_iterator(i);
|
|
|
|
const auto e = std::make_reverse_iterator(entities.begin());
|
|
|
|
if (std::find_if(j, e, intersects) != e) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
entities.insert(
|
|
|
|
i,
|
|
|
|
EntityInText(
|
|
|
|
EntityType::CustomUrl,
|
|
|
|
from,
|
|
|
|
till - from,
|
2021-11-18 13:03:12 +01:00
|
|
|
("internal:" + base + QString::number(time))));
|
2019-12-25 16:20:02 +01:00
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
|
2017-09-04 13:40:02 +02:00
|
|
|
return {};
|
|
|
|
}
|
2018-01-13 13:45:11 +01:00
|
|
|
|
2022-12-09 18:35:45 +01:00
|
|
|
not_null<Element*> Media::parent() const {
|
|
|
|
return _parent;
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
not_null<History*> Media::history() const {
|
2019-01-03 13:36:01 +01:00
|
|
|
return _parent->history();
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:37:59 +01:00
|
|
|
SelectedQuote Media::selectedQuote(TextSelection selection) const {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
bool Media::isDisplayed() const {
|
2018-01-17 17:21:01 +01:00
|
|
|
return true;
|
2018-01-13 13:45:11 +01:00
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
QSize Media::countCurrentSize(int newWidth) {
|
2018-01-13 13:45:11 +01:00
|
|
|
return QSize(qMin(newWidth, maxWidth()), minHeight());
|
|
|
|
}
|
|
|
|
|
2022-09-30 21:03:44 +02:00
|
|
|
void Media::fillImageShadow(
|
|
|
|
QPainter &p,
|
|
|
|
QRect rect,
|
|
|
|
Ui::BubbleRounding rounding,
|
|
|
|
const PaintContext &context) const {
|
|
|
|
const auto sti = context.imageStyle();
|
|
|
|
auto corners = Ui::CornersPixmaps();
|
|
|
|
const auto choose = [&](int index) -> QPixmap {
|
|
|
|
using Corner = Ui::BubbleCornerRounding;
|
|
|
|
switch (rounding[index]) {
|
|
|
|
case Corner::Large: return sti->msgShadowCornersLarge.p[index];
|
|
|
|
case Corner::Small: return sti->msgShadowCornersSmall.p[index];
|
|
|
|
}
|
|
|
|
return QPixmap();
|
|
|
|
};
|
|
|
|
corners.p[2] = choose(2);
|
|
|
|
corners.p[3] = choose(3);
|
|
|
|
Ui::FillRoundShadow(p, rect, sti->msgShadow, corners);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Media::fillImageOverlay(
|
|
|
|
QPainter &p,
|
|
|
|
QRect rect,
|
|
|
|
std::optional<Ui::BubbleRounding> rounding,
|
|
|
|
const PaintContext &context) const {
|
|
|
|
using Radius = Ui::CachedCornerRadius;
|
|
|
|
const auto &st = context.st;
|
|
|
|
if (!rounding) {
|
|
|
|
Ui::FillComplexOverlayRect(
|
|
|
|
p,
|
|
|
|
rect,
|
|
|
|
st->msgSelectOverlay(),
|
|
|
|
st->msgSelectOverlayCorners(Radius::Small));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
using Corner = Ui::BubbleCornerRounding;
|
|
|
|
auto corners = Ui::CornersPixmaps();
|
|
|
|
const auto lookup = [&](Corner corner) {
|
|
|
|
switch (corner) {
|
|
|
|
case Corner::None: return Radius::kCount;
|
|
|
|
case Corner::Small: return Radius::BubbleSmall;
|
|
|
|
case Corner::Large: return Radius::BubbleLarge;
|
|
|
|
}
|
|
|
|
Unexpected("Corner value in Document::fillThumbnailOverlay.");
|
|
|
|
};
|
|
|
|
for (auto i = 0; i != 4; ++i) {
|
|
|
|
const auto radius = lookup((*rounding)[i]);
|
|
|
|
corners.p[i] = (radius == Radius::kCount)
|
|
|
|
? QPixmap()
|
|
|
|
: st->msgSelectOverlayCorners(radius).p[i];
|
|
|
|
}
|
|
|
|
Ui::FillComplexOverlayRect(p, rect, st->msgSelectOverlay(), corners);
|
|
|
|
}
|
|
|
|
|
2022-12-12 19:57:17 +01:00
|
|
|
void Media::fillImageSpoiler(
|
|
|
|
QPainter &p,
|
|
|
|
not_null<MediaSpoiler*> spoiler,
|
|
|
|
QRect rect,
|
|
|
|
const PaintContext &context) const {
|
|
|
|
if (!spoiler->animation) {
|
|
|
|
spoiler->animation = std::make_unique<Ui::SpoilerAnimation>([=] {
|
|
|
|
_parent->customEmojiRepaint();
|
|
|
|
});
|
|
|
|
history()->owner().registerHeavyViewPart(_parent);
|
|
|
|
}
|
|
|
|
_parent->clearCustomEmojiRepaint();
|
2023-02-21 13:31:55 +01:00
|
|
|
const auto pausedSpoiler = context.paused
|
|
|
|
|| On(PowerSaving::kChatSpoiler);
|
2022-12-12 19:57:17 +01:00
|
|
|
Ui::FillSpoilerRect(
|
|
|
|
p,
|
|
|
|
rect,
|
|
|
|
MediaRoundingMask(spoiler->backgroundRounding),
|
|
|
|
Ui::DefaultImageSpoiler().frame(
|
2023-02-21 13:31:55 +01:00
|
|
|
spoiler->animation->index(context.now, pausedSpoiler)),
|
2022-12-12 19:57:17 +01:00
|
|
|
spoiler->cornerCache);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
|
|
|
|
const auto weak = base::make_weak(this);
|
|
|
|
spoiler->link = std::make_shared<LambdaClickHandler>([=](
|
|
|
|
const ClickContext &context) {
|
|
|
|
const auto button = context.button;
|
|
|
|
const auto media = weak.get();
|
|
|
|
if (button != Qt::LeftButton || !media || spoiler->revealed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto view = media->parent();
|
|
|
|
spoiler->revealed = true;
|
|
|
|
spoiler->revealAnimation.start([=] {
|
|
|
|
media->history()->owner().requestViewRepaint(view);
|
|
|
|
}, 0., 1., st::fadeWrapDuration);
|
|
|
|
media->history()->owner().requestViewRepaint(view);
|
|
|
|
media->history()->owner().registerShownSpoiler(view);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-11 15:13:04 +01:00
|
|
|
void Media::repaint() const {
|
|
|
|
history()->owner().requestViewRepaint(_parent);
|
|
|
|
}
|
|
|
|
|
2021-12-02 11:42:33 +01:00
|
|
|
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
2018-01-19 13:13:06 +01:00
|
|
|
if (item->emptyText()) {
|
2019-06-12 15:26:04 +02:00
|
|
|
return {};
|
2018-01-19 13:13:06 +01:00
|
|
|
}
|
|
|
|
const auto minResizeWidth = st::minPhotoSize
|
|
|
|
- st::msgPadding.left()
|
|
|
|
- st::msgPadding.right();
|
2019-06-12 15:26:04 +02:00
|
|
|
auto result = Ui::Text::String(minResizeWidth);
|
2020-11-10 17:38:21 +01:00
|
|
|
const auto context = Core::MarkedTextContext{
|
2022-06-24 08:49:11 +02:00
|
|
|
.session = &history()->session(),
|
2022-07-01 16:23:30 +02:00
|
|
|
.customEmojiRepaint = [=] { _parent->customEmojiRepaint(); },
|
2020-06-24 10:22:27 +02:00
|
|
|
};
|
2018-01-19 13:13:06 +01:00
|
|
|
result.setMarkedText(
|
|
|
|
st::messageTextStyle,
|
2023-01-26 16:36:43 +01:00
|
|
|
item->translatedTextWithLocalEntities(),
|
2020-06-24 10:22:27 +02:00
|
|
|
Ui::ItemTextOptions(item),
|
|
|
|
context);
|
2022-09-21 16:55:27 +02:00
|
|
|
FillTextWithAnimatedSpoilers(_parent, result);
|
2018-01-19 13:13:06 +01:00
|
|
|
if (const auto width = _parent->skipBlockWidth()) {
|
|
|
|
result.updateSkipBlock(width, _parent->skipBlockHeight());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
TextSelection Media::skipSelection(TextSelection selection) const {
|
|
|
|
return UnshiftItemSelection(selection, fullSelectionLength());
|
2018-01-13 13:45:11 +01:00
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
TextSelection Media::unskipSelection(TextSelection selection) const {
|
|
|
|
return ShiftItemSelection(selection, fullSelectionLength());
|
2018-01-13 13:45:11 +01:00
|
|
|
}
|
|
|
|
|
2021-08-19 16:22:12 +02:00
|
|
|
auto Media::getBubbleSelectionIntervals(
|
|
|
|
TextSelection selection) const
|
|
|
|
-> std::vector<Ui::BubbleSelectionInterval> {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2021-08-20 12:12:07 +02:00
|
|
|
bool Media::usesBubblePattern(const PaintContext &context) const {
|
|
|
|
return (context.selection != FullSelection)
|
|
|
|
&& _parent->hasOutLayout()
|
|
|
|
&& context.bubblesPattern
|
|
|
|
&& !context.viewport.isEmpty()
|
|
|
|
&& !context.bubblesPattern->pixmap.size().isEmpty();
|
|
|
|
}
|
2021-08-19 16:22:12 +02:00
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
PointState Media::pointState(QPoint point) const {
|
2018-01-27 14:59:24 +01:00
|
|
|
return QRect(0, 0, width(), height()).contains(point)
|
|
|
|
? PointState::Inside
|
|
|
|
: PointState::Outside;
|
|
|
|
}
|
|
|
|
|
2022-08-04 12:35:08 +02:00
|
|
|
std::unique_ptr<StickerPlayer> Media::stickerTakePlayer(
|
2020-07-02 12:44:12 +02:00
|
|
|
not_null<DocumentData*> data,
|
|
|
|
const Lottie::ColorReplacements *replacements) {
|
2020-06-16 18:53:44 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
TextState Media::getStateGrouped(
|
2018-01-13 13:45:11 +01:00
|
|
|
const QRect &geometry,
|
2019-12-20 15:13:55 +01:00
|
|
|
RectParts sides,
|
2018-01-13 13:45:11 +01:00
|
|
|
QPoint point,
|
2020-10-29 17:12:43 +01:00
|
|
|
StateRequest request) const {
|
2018-01-13 13:45:11 +01:00
|
|
|
Unexpected("Grouping method call.");
|
|
|
|
}
|
2019-08-02 15:21:09 +02:00
|
|
|
|
2022-09-30 21:03:44 +02:00
|
|
|
Ui::BubbleRounding Media::adjustedBubbleRounding(RectParts square) const {
|
|
|
|
auto result = bubbleRounding();
|
|
|
|
using Corner = Ui::BubbleCornerRounding;
|
|
|
|
const auto adjust = [&](bool round, Corner already, RectPart corner) {
|
|
|
|
return (already == Corner::Tail || !round || (square & corner))
|
|
|
|
? Corner::None
|
|
|
|
: already;
|
|
|
|
};
|
|
|
|
const auto top = isBubbleTop();
|
|
|
|
const auto bottom = isRoundedInBubbleBottom();
|
|
|
|
result.topLeft = adjust(top, result.topLeft, RectPart::TopLeft);
|
|
|
|
result.topRight = adjust(top, result.topRight, RectPart::TopRight);
|
|
|
|
result.bottomLeft = adjust(
|
|
|
|
bottom,
|
|
|
|
result.bottomLeft,
|
|
|
|
RectPart::BottomLeft);
|
|
|
|
result.bottomRight = adjust(
|
|
|
|
bottom,
|
|
|
|
result.bottomRight,
|
|
|
|
RectPart::BottomRight);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ui::BubbleRounding Media::adjustedBubbleRoundingWithCaption(
|
|
|
|
const Ui::Text::String &caption) const {
|
|
|
|
return adjustedBubbleRounding(
|
|
|
|
caption.isEmpty() ? RectParts() : RectPart::FullBottom);
|
|
|
|
}
|
|
|
|
|
2020-09-03 09:19:02 +02:00
|
|
|
bool Media::isRoundedInBubbleBottom() const {
|
2020-09-11 19:55:10 +02:00
|
|
|
return isBubbleBottom()
|
|
|
|
&& !_parent->data()->repliesAreComments()
|
|
|
|
&& !_parent->data()->externalReply();
|
2020-09-03 09:19:02 +02:00
|
|
|
}
|
|
|
|
|
2022-09-30 21:03:44 +02:00
|
|
|
Images::CornersMaskRef MediaRoundingMask(
|
|
|
|
std::optional<Ui::BubbleRounding> rounding) {
|
|
|
|
using Radius = Ui::CachedCornerRadius;
|
|
|
|
if (!rounding) {
|
|
|
|
return Images::CornersMaskRef(Ui::CachedCornersMasks(Radius::Small));
|
|
|
|
}
|
|
|
|
using Corner = Ui::BubbleCornerRounding;
|
|
|
|
auto result = Images::CornersMaskRef();
|
|
|
|
const auto &small = Ui::CachedCornersMasks(Radius::BubbleSmall);
|
|
|
|
const auto &large = Ui::CachedCornersMasks(Radius::BubbleLarge);
|
|
|
|
for (auto i = 0; i != 4; ++i) {
|
|
|
|
switch ((*rounding)[i]) {
|
|
|
|
case Corner::Small: result.p[i] = &small[i]; break;
|
|
|
|
case Corner::Large: result.p[i] = &large[i]; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:21:09 +02:00
|
|
|
} // namespace HistoryView
|