tdesktop/Telegram/SourceFiles/boxes/sticker_preview_box.cpp
2022-05-20 18:57:01 +04:00

305 lines
8.8 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 "boxes/sticker_preview_box.h"
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "data/data_file_origin.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/chat_theme.h"
#include "ui/layers/generic_box.h"
#include "ui/effects/premium_graphics.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
#include "settings/settings_premium.h"
#include "lottie/lottie_single_player.h"
#include "history/view/media/history_view_sticker.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_chat_helpers.h"
namespace {
constexpr auto kPremiumShift = 0.082;
struct Preload {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> media;
base::weak_ptr<Window::SessionController> controller;
};
[[nodiscard]] std::vector<Preload> &Preloads() {
static auto result = std::vector<Preload>();
return result;
}
void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
const auto origin = media->owner()->stickerSetOrigin();
media->automaticLoad(origin, nullptr);
media->videoThumbnailWanted(origin);
}
[[nodiscard]] object_ptr<Ui::RpWidget> StickerPreview(
QWidget *parent,
const std::shared_ptr<Data::DocumentMedia> &media,
const QImage &back,
int size) {
auto result = object_ptr<Ui::FixedHeightWidget>(parent, size);
const auto raw = result.data();
auto &lifetime = raw->lifetime();
struct State {
std::unique_ptr<Lottie::SinglePlayer> lottie;
std::unique_ptr<Lottie::SinglePlayer> effect;
};
const auto state = lifetime.make_state<State>();
using namespace HistoryView;
const auto document = media->owner();
const auto lottieSize = Sticker::Size(document);
const auto effectSize = Sticker::PremiumEffectSize(document);
const auto createLottieIfReady = [=] {
if (state->lottie) {
return;
}
const auto document = media->owner();
const auto sticker = document->sticker();
if (!sticker || !sticker->isLottie() || !media->loaded()) {
return;
} else if (media->videoThumbnailContent().isEmpty()) {
return;
}
const auto factor = style::DevicePixelRatio();
state->lottie = ChatHelpers::LottiePlayerFromDocument(
media.get(),
nullptr,
ChatHelpers::StickerLottieSize::MessageHistory,
lottieSize * factor,
Lottie::Quality::High);
state->effect = document->session().emojiStickersPack().effectPlayer(
document,
media->videoThumbnailContent(),
QString(),
true);
const auto update = [=] { raw->update(); };
auto &lifetime = raw->lifetime();
state->lottie->updates() | rpl::start_with_next(update, lifetime);
state->effect->updates() | rpl::start_with_next(update, lifetime);
};
raw->paintRequest(
) | rpl::start_with_next([=] {
createLottieIfReady();
auto p = QPainter(raw);
p.drawImage(0, 0, back);
if (!state->lottie
|| !state->lottie->ready()
|| !state->effect->ready()) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto frame = state->lottie->frameInfo({ lottieSize * factor });
const auto effect = state->effect->frameInfo(
{ effectSize * factor });
//const auto framesCount = !frame.image.isNull()
// ? state->lottie->framesCount()
// : 1;
//const auto effectsCount = !effect.image.isNull()
// ? state->effect->framesCount()
// : 1;
const auto left = effectSize.width()
- int(lottieSize.width() * (1. + kPremiumShift));
const auto top = (effectSize.height() - lottieSize.height()) / 2;
p.drawImage(
QRect(QPoint(left, top), lottieSize),
state->lottie->frame());
p.drawImage(raw->rect(), state->effect->frame());
if (!frame.image.isNull()/*
&& ((frame.index % effectsCount) <= effect.index)*/) {
state->lottie->markFrameShown();
}
if (!effect.image.isNull()/*
&& ((effect.index % framesCount) <= frame.index)*/) {
state->effect->markFrameShown();
}
}, lifetime);
return result;
}
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateGradientButton(
QWidget *parent,
QGradientStops stops) {
return object_ptr<Ui::GradientButton>(parent, std::move(stops));
}
[[nodiscard]] object_ptr<Ui::AbstractButton> CreatePremiumButton(
QWidget *parent) {
return CreateGradientButton(parent, Ui::Premium::ButtonGradientStops());
}
[[nodiscard]] object_ptr<Ui::AbstractButton> CreateUnlockButton(
QWidget *parent,
int width) {
auto result = CreatePremiumButton(parent);
const auto &st = st::premiumPreviewBox.button;
result->resize(width, st.height);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
result.data(),
tr::lng_sticker_premium_button(),
st::premiumPreviewButtonLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
rpl::combine(
result->widthValue(),
label->widthValue()
) | rpl::start_with_next([=](int outer, int width) {
label->moveToLeft(
(outer - width) / 2,
st::premiumPreviewBox.button.textTop,
outer);
}, label->lifetime());
return result;
}
void StickerBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const std::shared_ptr<Data::DocumentMedia> &media,
const QImage &back) {
const auto size = st::boxWideWidth;
box->setWidth(size);
box->setNoContentMargin(true);
box->addRow(StickerPreview(box, media, back, size), {});
const auto padding = st::premiumPreviewAboutPadding;
auto label = object_ptr<Ui::FlatLabel>(
box,
tr::lng_sticker_premium_about(),
st::premiumPreviewAbout);
label->resizeToWidth(size - padding.left() - padding.right());
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(label)),
padding);
box->setStyle(st::premiumPreviewBox);
const auto buttonPadding = st::premiumPreviewBox.buttonPadding;
const auto width = size - buttonPadding.left() - buttonPadding.right();
auto button = CreateUnlockButton(box, width);
button->setClickedCallback([=] {
controller->showSettings(Settings::PremiumId());
});
box->addButton(std::move(button));
}
void Show(
not_null<Window::SessionController*> controller,
const std::shared_ptr<Data::DocumentMedia> &media,
QImage back) {
controller->show(Box(StickerBox, controller, media, back));
}
void Show(not_null<Window::SessionController*> controller, QImage back) {
auto &list = Preloads();
for (auto i = begin(list); i != end(list);) {
const auto already = i->controller.get();
if (!already) {
i = list.erase(i);
} else if (already == controller) {
Show(controller, i->media, back);
i = list.erase(i);
return;
} else {
++i;
}
}
}
[[nodiscard]] QImage SolidColorImage(QSize size, QColor color) {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(color);
return result;
}
} // namespace
void ShowStickerPreviewBox(
not_null<Window::SessionController*> controller,
not_null<DocumentData*> document) {
auto &list = Preloads();
for (auto i = begin(list); i != end(list);) {
const auto already = i->controller.get();
if (!already) {
i = list.erase(i);
} else if (already == controller) {
if (i->document == document) {
return;
}
i->document = document;
i->media = document->createMediaView();
PreloadSticker(i->media);
return;
} else {
++i;
}
}
const auto weak = base::make_weak(controller.get());
list.push_back({
.document = document,
.media = document->createMediaView(),
.controller = weak,
});
PreloadSticker(list.back().media);
const auto fill = QSize(st::boxWideWidth, st::boxWideWidth);
const auto theme = controller->currentChatTheme();
const auto color = theme->background().colorForFill;
const auto area = QSize(fill.width(), fill.height() * 2);
const auto request = theme->cacheBackgroundRequest(area);
crl::async([=] {
using Option = Images::Option;
auto back = color
? SolidColorImage(fill, *color)
: request.background.waitingForNegativePattern()
? SolidColorImage(fill, Qt::black)
: Ui::CacheBackground(request).image;
const auto factor = style::DevicePixelRatio();
auto cropped = back.copy(QRect(
QPoint(0, fill.height() * factor / 2),
fill * factor));
cropped.setDevicePixelRatio(factor);
const auto options = Images::Options()
| Option::RoundSkipBottomLeft
| Option::RoundSkipBottomRight
| Option::RoundLarge;
const auto result = Images::Round(
std::move(cropped),
Images::CornersMask(st::boxRadius),
RectPart::TopLeft | RectPart::TopRight);
crl::on_main([=] {
if (const auto strong = weak.get()) {
Show(strong, result);
}
});
});
}