mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
1323 lines
38 KiB
C++
1323 lines
38 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/star_gift_cover_box.h"
|
|
|
|
#include "chat_helpers/stickers_lottie.h"
|
|
#include "core/application.h"
|
|
#include "core/ui_integration.h"
|
|
#include "main/main_session.h"
|
|
#include "window/window_controller.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_peer.h"
|
|
#include "data/data_session.h"
|
|
#include "data/stickers/data_custom_emoji.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "lottie/lottie_common.h"
|
|
#include "lottie/lottie_single_player.h"
|
|
#include "ui/effects/premium_stars_colored.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/text/format_values.h"
|
|
#include "ui/top_background_gradient.h"
|
|
#include "ui/vertical_list.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/wrap/fade_wrap.h"
|
|
#include "styles/style_credits.h"
|
|
#include "styles/style_giveaway.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr auto kCrossfadeDuration = crl::time(400);
|
|
constexpr auto kGradientButtonBgOpacity = 0.6;
|
|
|
|
constexpr auto kSpinnerBackdrops = 6;
|
|
constexpr auto kSpinnerPatterns = 6;
|
|
constexpr auto kSpinnerModels = 6;
|
|
|
|
constexpr auto kBackdropSpinDuration = crl::time(300);
|
|
constexpr auto kBackdropStopsAt = crl::time(2.5 * 1000);
|
|
constexpr auto kPatternSpinDuration = crl::time(600);
|
|
constexpr auto kPatternStopsAt = crl::time(4 * 1000);
|
|
constexpr auto kModelSpinDuration = crl::time(160);
|
|
constexpr auto kModelStopsAt = crl::time(5.5 * 1000);
|
|
constexpr auto kModelScaleFrom = 0.7;
|
|
|
|
struct AttributeSpin {
|
|
AttributeSpin(crl::time duration) : duration(duration) {
|
|
}
|
|
|
|
Animations::Simple animation;
|
|
crl::time duration = 0;
|
|
int wasIndex = -1;
|
|
int nowIndex = -1;
|
|
int willIndex = -1;
|
|
|
|
[[nodiscard]] float64 progress() const {
|
|
return animation.value(1.);
|
|
}
|
|
void startWithin(int count, Fn<void()> update) {
|
|
Expects(count > 0);
|
|
|
|
wasIndex = nowIndex;
|
|
nowIndex = willIndex;
|
|
willIndex = (willIndex < 0 ? 1 : (willIndex + 1)) % count;
|
|
animation.start(update, 0., 1., duration);
|
|
}
|
|
void startToTarget(Fn<void()> update, int slowdown = 1) {
|
|
if (willIndex != 0) {
|
|
wasIndex = nowIndex;
|
|
nowIndex = willIndex;
|
|
willIndex = 0;
|
|
animation.start(
|
|
update,
|
|
0.,
|
|
1.,
|
|
duration * 3 * slowdown,
|
|
anim::easeOutCubic);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct Released {
|
|
Released() : link(QColor(255, 255, 255)) {
|
|
}
|
|
|
|
rpl::variable<TextWithEntities> subtitleText;
|
|
std::optional<Premium::ColoredMiniStars> stars;
|
|
style::owned_color link;
|
|
style::FlatLabel st;
|
|
rpl::variable<PeerData*> by;
|
|
base::unique_qptr<FlatLabel> subtitle;
|
|
base::unique_qptr<AbstractButton> subtitleButton;
|
|
rpl::variable<int> subtitleHeight;
|
|
rpl::variable<bool> subtitleCustom;
|
|
bool outlined = false;
|
|
QColor bg;
|
|
QColor fg;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
struct UniqueGiftCoverWidget::BackdropView {
|
|
Data::UniqueGiftBackdrop colors;
|
|
QImage gradient;
|
|
};
|
|
|
|
struct UniqueGiftCoverWidget::PatternView {
|
|
DocumentData *document = nullptr;
|
|
std::unique_ptr<Text::CustomEmoji> emoji;
|
|
base::flat_map<int, base::flat_map<float64, QImage>> emojis;
|
|
};
|
|
|
|
struct UniqueGiftCoverWidget::ModelView {
|
|
std::shared_ptr<Data::DocumentMedia> media;
|
|
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
|
rpl::lifetime lifetime;
|
|
};
|
|
|
|
struct UniqueGiftCoverWidget::GiftView {
|
|
std::optional<Data::UniqueGift> gift;
|
|
BackdropView backdrop;
|
|
PatternView pattern;
|
|
ModelView model;
|
|
bool forced = false;
|
|
};
|
|
|
|
struct UniqueGiftCoverWidget::State {
|
|
std::shared_ptr<Data::GiftUpgradeSpinner> spinner;
|
|
Fn<void()> checkSpinnerStart;
|
|
GiftView now;
|
|
GiftView next;
|
|
Animations::Simple crossfade;
|
|
Animations::Simple heightAnimation;
|
|
std::vector<BackdropView> spinnerBackdrops;
|
|
std::vector<PatternView> spinnerPatterns;
|
|
std::vector<ModelView> spinnerModels;
|
|
AttributeSpin backdropSpin = kBackdropSpinDuration;
|
|
AttributeSpin patternSpin = kPatternSpinDuration;
|
|
AttributeSpin modelSpin = kModelSpinDuration;
|
|
crl::time spinStarted = 0;
|
|
int heightFinal = 0;
|
|
bool crossfading = false;
|
|
bool updateAttributesPending = false;
|
|
|
|
Released released;
|
|
|
|
FlatLabel *pretitle = nullptr;
|
|
FlatLabel *title = nullptr;
|
|
RpWidget *attrs = nullptr;
|
|
|
|
Fn<void(const Data::UniqueGift &)> updateAttrs;
|
|
Fn<void(float64)> updateColors;
|
|
Fn<void(const BackdropView &, const BackdropView &, float64)>
|
|
updateColorsFromBackdrops;
|
|
Fn<void(ModelView &, const Data::UniqueGiftModel &)> setupModel;
|
|
Fn<void(PatternView &, const Data::UniqueGiftPattern &)> setupPattern;
|
|
};
|
|
|
|
UniqueGiftCoverWidget::UniqueGiftCoverWidget(
|
|
QWidget *parent,
|
|
rpl::producer<UniqueGiftCover> data,
|
|
UniqueGiftCoverArgs &&args)
|
|
: RpWidget(parent)
|
|
, _state(std::make_unique<State>()) {
|
|
using SpinnerState = Data::GiftUpgradeSpinner::State;
|
|
|
|
_state->spinner = std::move(args.upgradeSpinner);
|
|
|
|
_state->setupModel = [this](
|
|
ModelView &to,
|
|
const Data::UniqueGiftModel &model) {
|
|
to.lifetime.destroy();
|
|
|
|
const auto document = model.document;
|
|
to.media = document->createMediaView();
|
|
to.media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
|
rpl::single() | rpl::then(
|
|
document->session().downloaderTaskFinished()
|
|
) | rpl::filter([&to] {
|
|
return to.media->loaded();
|
|
}) | rpl::on_next([this, &to] {
|
|
const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
|
|
to.lottie = ChatHelpers::LottiePlayerFromDocument(
|
|
to.media.get(),
|
|
ChatHelpers::StickerLottieSize::MessageHistory,
|
|
QSize(lottieSize, lottieSize),
|
|
Lottie::Quality::High);
|
|
|
|
to.lifetime.destroy();
|
|
const auto lottie = to.lottie.get();
|
|
lottie->updates() | rpl::on_next([this, lottie] {
|
|
if (_state->now.model.lottie.get() == lottie
|
|
|| _state->crossfade.animating()) {
|
|
update();
|
|
}
|
|
if (const auto onstack = _state->checkSpinnerStart) {
|
|
onstack();
|
|
}
|
|
}, to.lifetime);
|
|
}, to.lifetime);
|
|
};
|
|
|
|
_state->setupPattern = [this](
|
|
PatternView &to,
|
|
const Data::UniqueGiftPattern &pattern) {
|
|
const auto document = pattern.document;
|
|
const auto callback = [this, document] {
|
|
if (_state->now.pattern.document == document
|
|
|| _state->crossfade.animating()) {
|
|
update();
|
|
}
|
|
if (const auto onstack = _state->checkSpinnerStart) {
|
|
onstack();
|
|
}
|
|
};
|
|
to.document = document;
|
|
to.emoji = document->owner().customEmojiManager().create(
|
|
document,
|
|
callback,
|
|
Data::CustomEmojiSizeTag::Large);
|
|
[[maybe_unused]] const auto preload = to.emoji->ready();
|
|
};
|
|
|
|
if (const auto spinner = _state->spinner.get()) {
|
|
const auto fillBackdrops = [this, spinner] {
|
|
_state->spinnerBackdrops.clear();
|
|
_state->spinnerBackdrops.reserve(kSpinnerBackdrops);
|
|
const auto push = [this](const Data::UniqueGiftBackdrop &backdrop) {
|
|
if (_state->spinnerBackdrops.size() >= kSpinnerBackdrops) {
|
|
return false;
|
|
}
|
|
const auto already = ranges::contains(
|
|
_state->spinnerBackdrops,
|
|
backdrop,
|
|
&BackdropView::colors);
|
|
if (!already) {
|
|
_state->spinnerBackdrops.push_back({ backdrop });
|
|
}
|
|
return true;
|
|
};
|
|
push(spinner->target->backdrop);
|
|
for (const auto &backdrop : spinner->attributes.backdrops) {
|
|
if (!push(backdrop)) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
const auto fillPatterns = [this, spinner] {
|
|
_state->spinnerPatterns.clear();
|
|
_state->spinnerPatterns.reserve(kSpinnerPatterns);
|
|
const auto push = [this](const Data::UniqueGiftPattern &pattern) {
|
|
if (_state->spinnerPatterns.size() >= kSpinnerPatterns) {
|
|
return false;
|
|
}
|
|
const auto already = ranges::contains(
|
|
_state->spinnerPatterns,
|
|
pattern.document.get(),
|
|
&PatternView::document);
|
|
if (!already) {
|
|
_state->setupPattern(
|
|
_state->spinnerPatterns.emplace_back(),
|
|
pattern);
|
|
}
|
|
return true;
|
|
};
|
|
push(spinner->target->pattern);
|
|
for (const auto &pattern : spinner->attributes.patterns) {
|
|
if (!push(pattern)) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
const auto fillModels = [this, spinner] {
|
|
_state->spinnerModels.clear();
|
|
_state->spinnerModels.reserve(kSpinnerModels);
|
|
const auto push = [this](const Data::UniqueGiftModel &model) {
|
|
if (_state->spinnerModels.size() >= kSpinnerModels) {
|
|
return false;
|
|
}
|
|
const auto already = ranges::contains(
|
|
_state->spinnerModels,
|
|
model.document,
|
|
[](const ModelView &view) {
|
|
return view.media->owner();
|
|
});
|
|
if (!already) {
|
|
_state->setupModel(
|
|
_state->spinnerModels.emplace_back(),
|
|
model);
|
|
}
|
|
return true;
|
|
};
|
|
push(spinner->target->model);
|
|
for (const auto &model : spinner->attributes.models) {
|
|
if (!push(model)) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
_state->checkSpinnerStart = [this, spinner] {
|
|
if (spinner->state.current() != SpinnerState::Loading
|
|
|| _state->crossfading) {
|
|
return;
|
|
}
|
|
for (const auto &pattern : _state->spinnerPatterns) {
|
|
if (!pattern.emoji->ready()) {
|
|
return;
|
|
}
|
|
}
|
|
for (const auto &model : _state->spinnerModels) {
|
|
if (!model.lottie || !model.lottie->ready()) {
|
|
return;
|
|
}
|
|
}
|
|
spinner->state = SpinnerState::Prepared;
|
|
_state->checkSpinnerStart = nullptr;
|
|
};
|
|
spinner->state.value() | rpl::on_next([=](SpinnerState now) {
|
|
if (now == SpinnerState::Preparing) {
|
|
fillBackdrops();
|
|
fillPatterns();
|
|
fillModels();
|
|
spinner->state = SpinnerState::Loading;
|
|
if (!_state->crossfading) {
|
|
_state->next = {};
|
|
}
|
|
_state->checkSpinnerStart();
|
|
} else if (now == SpinnerState::Started) {
|
|
const auto repaint = [this] { update(); };
|
|
_state->backdropSpin.startWithin(
|
|
_state->spinnerBackdrops.size(),
|
|
repaint);
|
|
_state->patternSpin.startWithin(
|
|
_state->spinnerPatterns.size(),
|
|
repaint);
|
|
_state->modelSpin.startWithin(
|
|
_state->spinnerModels.size(),
|
|
repaint);
|
|
_state->spinStarted = crl::now();
|
|
}
|
|
}, lifetime());
|
|
}
|
|
|
|
rpl::duplicate(
|
|
data
|
|
) | rpl::on_next([this](const UniqueGiftCover &now) {
|
|
const auto setup = [&](GiftView &to) {
|
|
Expects(!now.spinner);
|
|
|
|
to = {};
|
|
to.gift = now.values;
|
|
to.forced = now.force;
|
|
to.backdrop.colors = now.values.backdrop;
|
|
_state->setupModel(to.model, now.values.model);
|
|
_state->setupPattern(to.pattern, now.values.pattern);
|
|
};
|
|
|
|
const auto spinner = _state->spinner.get();
|
|
if (!_state->now.gift) {
|
|
setup(_state->now);
|
|
update();
|
|
} else if (now.spinner) {
|
|
Assert(spinner != nullptr);
|
|
Assert(spinner->state.current() == SpinnerState::Prepared);
|
|
|
|
spinner->state = SpinnerState::Started;
|
|
} else if (!_state->next.gift || now.force) {
|
|
const auto spinnerState = spinner
|
|
? spinner->state.current()
|
|
: SpinnerState::Initial;
|
|
if (spinnerState == SpinnerState::Initial) {
|
|
setup(_state->next);
|
|
}
|
|
}
|
|
}, lifetime());
|
|
|
|
auto subtitleCustomText = args.subtitle
|
|
? std::move(args.subtitle)
|
|
: rpl::single(tr::marked());
|
|
_state->released.subtitleCustom = rpl::duplicate(
|
|
subtitleCustomText
|
|
) | rpl::map([](const TextWithEntities &custom) {
|
|
return !custom.empty();
|
|
});
|
|
|
|
const auto repaintedHook = args.repaintedHook;
|
|
const auto updateLinkFg = args.subtitleLinkColored;
|
|
_state->updateColorsFromBackdrops = [this, updateLinkFg](
|
|
const BackdropView &from,
|
|
const BackdropView &to,
|
|
float64 progress) {
|
|
_state->released.bg = (progress == 0.)
|
|
? from.colors.patternColor
|
|
: (progress == 1.)
|
|
? to.colors.patternColor
|
|
: anim::color(
|
|
from.colors.patternColor,
|
|
to.colors.patternColor,
|
|
progress);
|
|
const auto color = (progress == 0.)
|
|
? from.colors.textColor
|
|
: (progress == 1.)
|
|
? to.colors.textColor
|
|
: anim::color(
|
|
from.colors.textColor,
|
|
to.colors.textColor,
|
|
progress);
|
|
if (updateLinkFg) {
|
|
const auto &subtitleCustom = _state->released.subtitleCustom;
|
|
_state->released.link.update(subtitleCustom.current()
|
|
? color
|
|
: QColor(255, 255, 255));
|
|
}
|
|
_state->released.fg = color;
|
|
_state->released.subtitle->setTextColorOverride(color);
|
|
};
|
|
_state->updateColors = [this, repaintedHook](float64 progress) {
|
|
if (repaintedHook) {
|
|
repaintedHook(_state->now.gift, _state->next.gift, progress);
|
|
}
|
|
_state->updateColorsFromBackdrops(
|
|
_state->now.backdrop,
|
|
_state->next.backdrop,
|
|
progress);
|
|
};
|
|
|
|
if (args.resalePrice) {
|
|
auto background = rpl::duplicate(
|
|
data
|
|
) | rpl::map([](const UniqueGiftCover &cover) {
|
|
auto result = cover.values.backdrop.patternColor;
|
|
result.setAlphaF(kGradientButtonBgOpacity * result.alphaF());
|
|
return result;
|
|
});
|
|
SetupResalePriceButton(
|
|
this,
|
|
std::move(background),
|
|
std::move(args.resalePrice),
|
|
std::move(args.resaleClick));
|
|
}
|
|
|
|
_state->pretitle = args.pretitle
|
|
? CreateChild<FlatLabel>(
|
|
this,
|
|
std::move(args.pretitle),
|
|
st::uniqueGiftPretitle)
|
|
: nullptr;
|
|
if (_state->pretitle) {
|
|
_state->released.stars.emplace(
|
|
this,
|
|
true,
|
|
Premium::MiniStarsType::SlowStars);
|
|
const auto white = QColor(255, 255, 255);
|
|
_state->released.stars->setColorOverride(QGradientStops{
|
|
{ 0., anim::with_alpha(white, .3) },
|
|
{ 1., white },
|
|
});
|
|
_state->pretitle->geometryValue() | rpl::on_next([this](QRect rect) {
|
|
const auto half = rect.height() / 2;
|
|
_state->released.stars->setCenter(rect - QMargins(half, 0, half, 0));
|
|
}, _state->pretitle->lifetime());
|
|
_state->pretitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
_state->pretitle->setTextColorOverride(QColor(255, 255, 255));
|
|
_state->pretitle->paintOn([this](QPainter &p) {
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
const auto radius = _state->pretitle->height() / 2.;
|
|
p.setPen(Qt::NoPen);
|
|
auto bg = _state->released.bg;
|
|
bg.setAlphaF(kGradientButtonBgOpacity * bg.alphaF());
|
|
p.setBrush(bg);
|
|
p.drawRoundedRect(_state->pretitle->rect(), radius, radius);
|
|
p.translate(-_state->pretitle->pos());
|
|
_state->released.stars->paint(p);
|
|
});
|
|
}
|
|
|
|
_state->title = CreateChild<FlatLabel>(
|
|
this,
|
|
rpl::duplicate(
|
|
data
|
|
) | rpl::map([](const UniqueGiftCover &now) {
|
|
return now.values.title;
|
|
}),
|
|
st::uniqueGiftTitle);
|
|
_state->title->setTextColorOverride(QColor(255, 255, 255));
|
|
|
|
_state->released.by = rpl::duplicate(
|
|
data
|
|
) | rpl::map([](const UniqueGiftCover &cover) {
|
|
return cover.values.releasedBy;
|
|
});
|
|
_state->released.subtitleText = rpl::combine(
|
|
std::move(subtitleCustomText),
|
|
rpl::duplicate(data)
|
|
) | rpl::map([](TextWithEntities custom, const UniqueGiftCover &cover) {
|
|
if (!custom.empty()) {
|
|
return custom;
|
|
}
|
|
const auto &gift = cover.values;
|
|
return gift.releasedBy
|
|
? tr::lng_gift_unique_number_by(
|
|
tr::now,
|
|
lt_index,
|
|
tr::marked(QString::number(gift.number)),
|
|
lt_name,
|
|
tr::link('@' + gift.releasedBy->username()),
|
|
tr::marked)
|
|
: tr::lng_gift_unique_number(
|
|
tr::now,
|
|
lt_index,
|
|
tr::marked(QString::number(gift.number)),
|
|
tr::marked);
|
|
});
|
|
|
|
const auto subtitleOutlined = args.subtitleOutlined;
|
|
const auto subtitleClick = args.subtitleClick;
|
|
rpl::combine(
|
|
_state->released.by.value(),
|
|
_state->released.subtitleCustom.value()
|
|
) | rpl::on_next([=](PeerData *by, bool subtitleCustom) {
|
|
_state->released.outlined = by
|
|
|| (subtitleCustom && subtitleOutlined);
|
|
_state->released.st = _state->released.outlined
|
|
? st::uniqueGiftReleasedBy
|
|
: st::uniqueGiftSubtitle;
|
|
_state->released.st.palette.linkFg = _state->released.link.color();
|
|
|
|
const auto session = &_state->now.gift->model.document->session();
|
|
_state->released.subtitle = base::make_unique_q<FlatLabel>(
|
|
this,
|
|
_state->released.subtitleText.value(),
|
|
_state->released.st,
|
|
st::defaultPopupMenu,
|
|
Core::TextContext({ .session = session }));
|
|
const auto subtitle = _state->released.subtitle.get();
|
|
subtitle->show();
|
|
|
|
widthValue(
|
|
) | rpl::on_next([=](int width) {
|
|
const auto skip = st::uniqueGiftBottom;
|
|
if (width <= 3 * skip) {
|
|
return;
|
|
}
|
|
const auto available = width - 2 * skip;
|
|
subtitle->resizeToWidth(available);
|
|
}, subtitle->lifetime());
|
|
_state->released.subtitleHeight.force_assign(subtitle->height());
|
|
_state->released.subtitleHeight = subtitle->heightValue();
|
|
|
|
const auto handler = subtitleCustom ? subtitleClick : Fn<void()>();
|
|
if (!subtitleOutlined && handler) {
|
|
subtitle->setClickHandlerFilter([=](const auto &...) {
|
|
subtitleClick();
|
|
return false;
|
|
});
|
|
} else if (_state->released.outlined) {
|
|
_state->released.subtitleButton = base::make_unique_q<AbstractButton>(
|
|
this);
|
|
const auto button = _state->released.subtitleButton.get();
|
|
|
|
button->show();
|
|
subtitle->raise();
|
|
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
button->setClickedCallback(handler
|
|
? handler
|
|
: [=] { GiftReleasedByHandler(by); });
|
|
subtitle->geometryValue(
|
|
) | rpl::on_next([=](QRect geometry) {
|
|
button->setGeometry(
|
|
geometry.marginsAdded(st::giftBoxReleasedByMargin));
|
|
}, button->lifetime());
|
|
button->paintRequest() | rpl::on_next([=] {
|
|
auto p = QPainter(button);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
const auto use = subtitle->textMaxWidth();
|
|
const auto add = button->width() - subtitle->width();
|
|
const auto full = use + add;
|
|
const auto left = (button->width() - full) / 2;
|
|
const auto height = button->height();
|
|
const auto radius = height / 2.;
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(_state->released.bg);
|
|
p.setOpacity(0.5);
|
|
p.drawRoundedRect(left, 0, full, height, radius, radius);
|
|
}, button->lifetime());
|
|
} else {
|
|
_state->released.subtitleButton = nullptr;
|
|
}
|
|
_state->updateColors(_state->crossfade.value(
|
|
_state->crossfading ? 1. : 0.));
|
|
}, _state->title->lifetime());
|
|
|
|
_state->attrs = args.attributesInfo
|
|
? CreateChild<RpWidget>(this)
|
|
: nullptr;
|
|
_state->updateAttrs = [](const Data::UniqueGift &) {};
|
|
if (_state->attrs) {
|
|
struct AttributeState {
|
|
Text::String name;
|
|
Text::String type;
|
|
Text::String percent;
|
|
};
|
|
struct AttributesState {
|
|
AttributeState model;
|
|
AttributeState pattern;
|
|
AttributeState backdrop;
|
|
};
|
|
const auto astate = lifetime().make_state<AttributesState>();
|
|
const auto setType = [&](AttributeState &state, tr::phrase<> text) {
|
|
state.type = Text::String(
|
|
st::uniqueAttributeType,
|
|
text(tr::now));
|
|
};
|
|
setType(astate->model, tr::lng_auction_preview_model);
|
|
setType(astate->pattern, tr::lng_auction_preview_symbol);
|
|
setType(astate->backdrop, tr::lng_auction_preview_backdrop);
|
|
|
|
_state->updateAttrs = [this, astate](const Data::UniqueGift &gift) {
|
|
const auto set = [&](
|
|
AttributeState &state,
|
|
const Data::UniqueGiftAttribute &value) {
|
|
state.name = Text::String(
|
|
st::uniqueAttributeName,
|
|
value.name);
|
|
state.percent = Text::String(
|
|
st::uniqueAttributePercent,
|
|
Data::UniqueGiftAttributeText(value));
|
|
};
|
|
set(astate->model, gift.model);
|
|
set(astate->pattern, gift.pattern);
|
|
set(astate->backdrop, gift.backdrop);
|
|
_state->attrs->update();
|
|
};
|
|
const auto attrsHeight = st::uniqueAttributeTop
|
|
+ st::uniqueAttributePadding.top()
|
|
+ st::uniqueAttributeName.font->height
|
|
+ st::uniqueAttributeType.font->height
|
|
+ st::uniqueAttributePadding.bottom();
|
|
_state->attrs->resize(_state->attrs->width(), attrsHeight);
|
|
_state->attrs->paintOn([this, astate, attrsHeight](QPainter &p) {
|
|
const auto skip = st::giftBoxGiftSkip.x();
|
|
const auto available = _state->attrs->width() - 2 * skip;
|
|
const auto single = available / 3;
|
|
if (single <= 0) {
|
|
return;
|
|
}
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
auto bg = _state->released.bg;
|
|
bg.setAlphaF(kGradientButtonBgOpacity * bg.alphaF());
|
|
const auto innert = st::uniqueAttributeTop;
|
|
const auto innerh = attrsHeight - innert;
|
|
const auto radius = innerh / 3.;
|
|
const auto paint = [&](int x, const AttributeState &state) {
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(bg);
|
|
p.drawRoundedRect(x, innert, single, innerh, radius, radius);
|
|
p.setPen(QColor(255, 255, 255));
|
|
const auto padding = st::uniqueAttributePadding;
|
|
const auto inner = single - padding.left() - padding.right();
|
|
const auto namew = std::min(inner, state.name.maxWidth());
|
|
state.name.draw(p, {
|
|
.position = QPoint(
|
|
x + (single - namew) / 2,
|
|
innert + padding.top()),
|
|
.availableWidth = namew,
|
|
.elisionLines = 1,
|
|
});
|
|
p.setPen(_state->released.fg);
|
|
const auto typew = std::min(inner, state.type.maxWidth());
|
|
state.type.draw(p, {
|
|
.position = QPoint(
|
|
x + (single - typew) / 2,
|
|
innert + padding.top() + state.name.minHeight()),
|
|
.availableWidth = typew,
|
|
});
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(anim::color(_state->released.bg, _state->released.fg, 0.3));
|
|
const auto r = st::uniqueAttributePercent.font->height / 2.;
|
|
const auto left = x + single - state.percent.maxWidth();
|
|
const auto top = st::uniqueAttributePercentPadding.top();
|
|
const auto percent = QRect(
|
|
left,
|
|
top,
|
|
state.percent.maxWidth(),
|
|
st::uniqueAttributeType.font->height);
|
|
p.drawRoundedRect(
|
|
percent.marginsAdded(st::uniqueAttributePercentPadding),
|
|
r,
|
|
r);
|
|
p.setPen(QColor(255, 255, 255));
|
|
state.percent.draw(p, {
|
|
.position = percent.topLeft(),
|
|
});
|
|
};
|
|
auto left = 0;
|
|
paint(left, astate->model);
|
|
paint(left + single + skip, astate->backdrop);
|
|
paint(_state->attrs->width() - single - left, astate->pattern);
|
|
});
|
|
}
|
|
_state->updateAttrs(*_state->now.gift);
|
|
|
|
rpl::combine(
|
|
widthValue(),
|
|
_state->released.subtitleHeight.value()
|
|
) | rpl::on_next([this](int width, int subtitleHeight) {
|
|
const auto skip = st::uniqueGiftBottom;
|
|
if (width <= 3 * skip) {
|
|
return;
|
|
}
|
|
const auto available = width - 2 * skip;
|
|
_state->title->resizeToWidth(available);
|
|
|
|
auto top = st::uniqueGiftTitleTop;
|
|
if (_state->pretitle) {
|
|
_state->pretitle->move((width - _state->pretitle->width()) / 2, top);
|
|
top += _state->pretitle->height()
|
|
+ (st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop)
|
|
- _state->title->height();
|
|
}
|
|
|
|
_state->title->moveToLeft(skip, top);
|
|
if (_state->pretitle) {
|
|
top += _state->title->height() + st::defaultVerticalListSkip;
|
|
} else {
|
|
top += st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop;
|
|
}
|
|
|
|
_state->released.subtitle->moveToLeft(skip, top);
|
|
top += subtitleHeight + (skip / 2);
|
|
|
|
if (_state->attrs) {
|
|
_state->attrs->resizeToWidth(width
|
|
- st::giftBoxPadding.left()
|
|
- st::giftBoxPadding.right());
|
|
_state->attrs->moveToLeft(st::giftBoxPadding.left(), top);
|
|
top += _state->attrs->height() + (skip / 2);
|
|
} else {
|
|
top += (skip / 2);
|
|
}
|
|
if (!height() || height() == top) {
|
|
resize(width, top);
|
|
} else {
|
|
_state->heightFinal = top;
|
|
_state->heightAnimation.start([this, width, top] {
|
|
resize(
|
|
width,
|
|
int(base::SafeRound(_state->heightAnimation.value(top))));
|
|
}, height(), top, st::slideWrapDuration);
|
|
}
|
|
}, lifetime());
|
|
}
|
|
|
|
void UniqueGiftCoverWidget::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
auto progress = _state->crossfade.value(_state->crossfading ? 1. : 0.);
|
|
if (_state->updateAttributesPending && progress >= 0.5) {
|
|
_state->updateAttributesPending = false;
|
|
_state->updateAttrs(*_state->next.gift);
|
|
} else if (_state->updateAttributesPending
|
|
&& !_state->crossfade.animating()) {
|
|
_state->updateAttributesPending = false;
|
|
_state->updateAttrs(*_state->now.gift);
|
|
}
|
|
if (_state->crossfading) {
|
|
_state->updateColors(progress);
|
|
}
|
|
if (progress == 1.) {
|
|
_state->crossfading = false;
|
|
_state->now = base::take(_state->next);
|
|
progress = 0.;
|
|
}
|
|
|
|
const auto context = PaintContext{
|
|
.width = width(),
|
|
.patternAreaHeight = st::uniqueGiftSubtitleTop,
|
|
};
|
|
if (_state->spinStarted) {
|
|
paintSpinnerAnimation(p, context);
|
|
} else {
|
|
paintNormalAnimation(p, context, progress);
|
|
}
|
|
}
|
|
|
|
UniqueGiftCoverWidget::~UniqueGiftCoverWidget() = default;
|
|
|
|
QImage UniqueGiftCoverWidget::prepareBackdrop(
|
|
BackdropView &backdrop,
|
|
const PaintContext &context) {
|
|
const auto ratio = style::DevicePixelRatio();
|
|
const auto gradientSize = QSize(
|
|
context.width,
|
|
std::max(height(), _state->heightFinal));
|
|
if (backdrop.gradient.size() != gradientSize * ratio) {
|
|
backdrop.gradient = CreateTopBgGradient(
|
|
gradientSize,
|
|
backdrop.colors);
|
|
}
|
|
return backdrop.gradient;
|
|
}
|
|
|
|
void UniqueGiftCoverWidget::paintBackdrop(
|
|
QPainter &p,
|
|
BackdropView &backdrop,
|
|
const PaintContext &context) {
|
|
p.drawImage(0, 0, prepareBackdrop(backdrop, context));
|
|
}
|
|
|
|
void UniqueGiftCoverWidget::paintPattern(
|
|
QPainter &p,
|
|
PatternView &pattern,
|
|
const BackdropView &backdrop,
|
|
const PaintContext &context,
|
|
float64 shown) {
|
|
const auto color = backdrop.colors.patternColor;
|
|
const auto key = (color.red() << 16)
|
|
| (color.green() << 8)
|
|
| color.blue();
|
|
PaintBgPoints(
|
|
p,
|
|
PatternBgPoints(),
|
|
pattern.emojis[key],
|
|
pattern.emoji.get(),
|
|
color,
|
|
QRect(0, 0, context.width, context.patternAreaHeight),
|
|
shown);
|
|
}
|
|
|
|
bool UniqueGiftCoverWidget::paintModel(
|
|
QPainter &p,
|
|
ModelView &model,
|
|
const PaintContext &context,
|
|
float64 scale,
|
|
bool paused) {
|
|
const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
|
|
const auto lottie = model.lottie.get();
|
|
const auto factor = style::DevicePixelRatio();
|
|
const auto request = Lottie::FrameRequest{
|
|
.box = QSize(lottieSize, lottieSize) * factor,
|
|
};
|
|
const auto frame = (lottie && lottie->ready())
|
|
? lottie->frameInfo(request)
|
|
: Lottie::Animation::FrameInfo();
|
|
if (frame.image.isNull()) {
|
|
return false;
|
|
}
|
|
const auto size = frame.image.size() / factor;
|
|
const auto rect = QRect(
|
|
QPoint((context.width - size.width()) / 2, st::uniqueGiftModelTop),
|
|
size);
|
|
if (scale < 1.) {
|
|
const auto origin = rect.center();
|
|
p.translate(origin);
|
|
p.scale(scale, scale);
|
|
p.translate(-origin);
|
|
}
|
|
p.drawImage(rect, frame.image);
|
|
const auto count = lottie->framesCount();
|
|
const auto finished = lottie->frameIndex() == (count - 1);
|
|
if (!paused) {
|
|
lottie->markFrameShown();
|
|
}
|
|
return finished;
|
|
}
|
|
|
|
QRect UniqueGiftCoverWidget::prepareCraftFrame(
|
|
QImage &canvas,
|
|
const CraftContext &context) {
|
|
const auto full = this->size();
|
|
const auto ratio = style::DevicePixelRatio();
|
|
|
|
if (canvas.size() != full * ratio) {
|
|
canvas = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied);
|
|
canvas.setDevicePixelRatio(ratio);
|
|
}
|
|
|
|
LOG(("FULL: %1x%2").arg(full.width()).arg(full.height()));
|
|
|
|
const auto expand = context.expandProgress;
|
|
const auto size = QSize(
|
|
anim::interpolate(context.initial.width(), full.width(), expand),
|
|
anim::interpolate(context.initial.height(), full.height(), expand));
|
|
const auto rect = QRect(QPoint(), size);
|
|
auto p = QPainter(&canvas);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
const auto radius = st::boxRadius * expand;
|
|
const auto bgProgress = context.fadeInProgress;
|
|
|
|
auto gradient = QRadialGradient(
|
|
rect.center(),
|
|
size.height() / 2);
|
|
const auto center = anim::color(
|
|
context.initialBg,
|
|
_state->now.backdrop.colors.centerColor,
|
|
bgProgress);
|
|
const auto edge = anim::color(
|
|
context.initialBg,
|
|
_state->now.backdrop.colors.edgeColor,
|
|
bgProgress);
|
|
gradient.setStops({ { 0., center }, { 1., edge } });
|
|
p.setPen(Qt::NoPen);
|
|
if (radius > 0.) {
|
|
const auto more = qCeil(radius * 2);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.fillRect(0, 0, more, more, Qt::transparent);
|
|
p.fillRect(rect.width() - more, 0, more, more, Qt::transparent);
|
|
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
p.setBrush(gradient);
|
|
p.drawRoundedRect(
|
|
rect.marginsAdded({ 0, 0, 0, more }),
|
|
radius,
|
|
radius);
|
|
} else {
|
|
p.fillRect(rect, gradient);
|
|
}
|
|
|
|
const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
|
|
const auto paused = (expand == 0.);
|
|
const auto modelScale = expand
|
|
+ ((st::giftBoxStickerTiny.width() / float64(lottieSize))
|
|
* (1. - expand));
|
|
const auto finalShift = (size.height() - lottieSize) / 2;
|
|
const auto shift = (1. - expand) * (finalShift - st::uniqueGiftModelTop);
|
|
p.translate(0, shift);
|
|
paintModel(p, _state->now.model, {
|
|
.width = size.width(),
|
|
}, modelScale, paused);
|
|
p.translate(0, -shift);
|
|
|
|
const auto skip = anim::interpolate(size.width() / 3, 0, expand);
|
|
const auto pointsHeight = anim::interpolate(
|
|
context.initial.height(),
|
|
st::uniqueGiftSubtitleTop,
|
|
expand);
|
|
p.translate(-skip, 0);
|
|
paintPattern(p, _state->now.pattern, _state->now.backdrop, {
|
|
.width = size.width() + 2 * skip,
|
|
.patternAreaHeight = pointsHeight,
|
|
}, bgProgress);
|
|
|
|
return QRect(QPoint(), size * ratio);
|
|
}
|
|
|
|
bool UniqueGiftCoverWidget::paintGift(
|
|
QPainter &p,
|
|
GiftView &gift,
|
|
const PaintContext &context,
|
|
float64 shown) {
|
|
Expects(gift.gift.has_value());
|
|
|
|
paintBackdrop(p, gift.backdrop, context);
|
|
if (gift.gift->pattern.document != gift.gift->model.document) {
|
|
paintPattern(p, gift.pattern, gift.backdrop, context, shown);
|
|
}
|
|
return paintModel(p, gift.model, context);
|
|
}
|
|
|
|
QColor UniqueGiftCoverWidget::backgroundColor() const {
|
|
return _state->released.bg;
|
|
}
|
|
|
|
QColor UniqueGiftCoverWidget::foregroundColor() const {
|
|
return _state->released.fg;
|
|
}
|
|
|
|
void UniqueGiftCoverWidget::paintSpinnerAnimation(
|
|
QPainter &p,
|
|
const PaintContext &context) {
|
|
using SpinnerState = Data::GiftUpgradeSpinner::State;
|
|
|
|
const auto select = [&](auto &&list, int index, auto &fallback)
|
|
-> decltype(auto) {
|
|
return (index >= 0) ? list[index] : fallback;
|
|
};
|
|
|
|
const auto now = crl::now();
|
|
const auto elapsed = now - _state->spinStarted;
|
|
auto current = _state->spinner->state.current();
|
|
const auto stateTo = [&](SpinnerState to) {
|
|
if (int(current) < int(to)) {
|
|
_state->spinner->state = current = to;
|
|
}
|
|
};
|
|
const auto switchTo = [&](SpinnerState to, crl::time at) {
|
|
if (elapsed >= at) {
|
|
stateTo(to);
|
|
}
|
|
};
|
|
const auto actualize = [&](
|
|
AttributeSpin &spin,
|
|
auto &&list,
|
|
SpinnerState checkState,
|
|
SpinnerState finishState,
|
|
int slowdown = 1) {
|
|
if (spin.progress() < 1.) {
|
|
return;
|
|
} else if (current >= checkState) {
|
|
spin.startToTarget([this] { update(); }, slowdown);
|
|
stateTo(finishState);
|
|
} else {
|
|
spin.startWithin(list.size(), [this] { update(); });
|
|
}
|
|
};
|
|
if (anim::Disabled() && current < SpinnerState::FinishedModel) {
|
|
stateTo(SpinnerState::FinishedModel);
|
|
_state->backdropSpin.startToTarget([this] { update(); });
|
|
_state->patternSpin.startToTarget([this] { update(); });
|
|
_state->modelSpin.startToTarget([this] { update(); });
|
|
}
|
|
if (_state->backdropSpin.willIndex != 0) {
|
|
switchTo(SpinnerState::FinishBackdrop, kBackdropStopsAt);
|
|
}
|
|
actualize(
|
|
_state->backdropSpin,
|
|
_state->spinnerBackdrops,
|
|
SpinnerState::FinishBackdrop,
|
|
SpinnerState::FinishedBackdrop);
|
|
|
|
if (current == SpinnerState::FinishedBackdrop
|
|
&& _state->patternSpin.willIndex != 0) {
|
|
switchTo(SpinnerState::FinishPattern, kPatternStopsAt);
|
|
}
|
|
actualize(
|
|
_state->patternSpin,
|
|
_state->spinnerPatterns,
|
|
SpinnerState::FinishPattern,
|
|
SpinnerState::FinishedPattern);
|
|
|
|
if (current == SpinnerState::FinishedPattern
|
|
&& _state->modelSpin.willIndex != 0) {
|
|
switchTo(SpinnerState::FinishModel, kModelStopsAt);
|
|
}
|
|
actualize(
|
|
_state->modelSpin,
|
|
_state->spinnerModels,
|
|
SpinnerState::FinishModel,
|
|
SpinnerState::FinishedModel,
|
|
2);
|
|
|
|
auto &backdropNow = select(
|
|
_state->spinnerBackdrops,
|
|
_state->backdropSpin.nowIndex,
|
|
_state->now.backdrop);
|
|
auto &backdropWill = select(
|
|
_state->spinnerBackdrops,
|
|
_state->backdropSpin.willIndex,
|
|
_state->now.backdrop);
|
|
const auto backdropProgress = _state->backdropSpin.progress();
|
|
_state->updateColorsFromBackdrops(
|
|
backdropNow,
|
|
backdropWill,
|
|
backdropProgress);
|
|
|
|
auto &patternNow = select(
|
|
_state->spinnerPatterns,
|
|
_state->patternSpin.nowIndex,
|
|
_state->now.pattern);
|
|
auto &patternWill = select(
|
|
_state->spinnerPatterns,
|
|
_state->patternSpin.willIndex,
|
|
_state->now.pattern);
|
|
const auto patternProgress = _state->patternSpin.progress();
|
|
const auto paintPatterns = [&](
|
|
QPainter &p,
|
|
const BackdropView &backdrop) {
|
|
if (patternProgress >= 1.) {
|
|
paintPattern(p, patternWill, backdrop, context, 1.);
|
|
} else {
|
|
paintPattern(p, patternNow, backdrop, context, 1. - patternProgress);
|
|
if (patternProgress > 0.) {
|
|
paintPattern(p, patternWill, backdrop, context, patternProgress);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (backdropProgress >= 1.) {
|
|
p.drawImage(0, 0, prepareBackdrop(backdropWill, context));
|
|
paintPatterns(p, backdropWill);
|
|
} else {
|
|
p.drawImage(0, 0, prepareBackdrop(backdropNow, context));
|
|
paintPatterns(p, backdropNow);
|
|
|
|
const auto fade = context.width / 2;
|
|
const auto from = anim::interpolate(
|
|
-fade,
|
|
context.width,
|
|
backdropProgress);
|
|
if (const auto till = from + fade; till > 0) {
|
|
auto faded = prepareBackdrop(backdropWill, context);
|
|
auto q = QPainter(&faded);
|
|
paintPatterns(q, backdropWill);
|
|
|
|
q.setCompositionMode(
|
|
QPainter::CompositionMode_DestinationIn);
|
|
auto brush = QLinearGradient(from, 0, till, 0);
|
|
brush.setStops({
|
|
{ 0., QColor(255, 255, 255, 255) },
|
|
{ 1., QColor(255, 255, 255, 0) },
|
|
});
|
|
const auto ratio = int(faded.devicePixelRatio());
|
|
const auto imgHeight = faded.height() / ratio;
|
|
q.fillRect(from, 0, fade, imgHeight, brush);
|
|
q.end();
|
|
|
|
p.drawImage(
|
|
QRect(0, 0, till, imgHeight),
|
|
faded,
|
|
QRect(0, 0, till * ratio, faded.height()));
|
|
}
|
|
}
|
|
|
|
auto &modelWas = select(
|
|
_state->spinnerModels,
|
|
_state->modelSpin.wasIndex,
|
|
_state->now.model);
|
|
auto &modelNow = select(
|
|
_state->spinnerModels,
|
|
_state->modelSpin.nowIndex,
|
|
_state->now.model);
|
|
auto &modelWill = select(
|
|
_state->spinnerModels,
|
|
_state->modelSpin.willIndex,
|
|
_state->now.model);
|
|
const auto modelProgress = _state->modelSpin.progress();
|
|
const auto paintOneModel = [&](ModelView &view, float64 progress) {
|
|
if (progress >= 1. || progress <= -1.) {
|
|
return;
|
|
}
|
|
auto scale = 1.;
|
|
if (progress != 0.) {
|
|
const auto shift = progress * context.width / 2.;
|
|
const auto shown = 1. - std::abs(progress);
|
|
scale = kModelScaleFrom + shown * (1. - kModelScaleFrom);
|
|
p.save();
|
|
p.setOpacity(shown);
|
|
p.translate(int(base::SafeRound(shift)), 0);
|
|
}
|
|
paintModel(p, view, context, scale, true);
|
|
if (progress != 0.) {
|
|
p.restore();
|
|
}
|
|
};
|
|
const auto initial = (_state->modelSpin.nowIndex < 0);
|
|
const auto ending = (current == SpinnerState::FinishedModel)
|
|
&& !_state->modelSpin.willIndex;
|
|
const auto willProgress = ending
|
|
? (modelProgress - 1.)
|
|
: (-1. + modelProgress * 2 / 3.);
|
|
const auto nowProgress = ending
|
|
? (modelProgress * 4 / 3. - 1 / 3.)
|
|
: initial
|
|
? (modelProgress * 1 / 3.)
|
|
: (willProgress + 2 / 3.);
|
|
const auto wasProgress = ending
|
|
? std::min(nowProgress + 2 / 3., 1.)
|
|
: 1. + (modelProgress - 1.) * 2 / 3.;
|
|
if (!initial) {
|
|
paintOneModel(modelWas, wasProgress);
|
|
}
|
|
paintOneModel(modelNow, nowProgress);
|
|
paintOneModel(modelWill, willProgress);
|
|
|
|
if (anim::Disabled()
|
|
|| (ending
|
|
&& modelProgress >= 1.
|
|
&& backdropProgress >= 1.
|
|
&& patternProgress >= 1.)) {
|
|
const auto take = [&](auto &&list) {
|
|
auto result = std::move(list.front());
|
|
list.clear();
|
|
return result;
|
|
};
|
|
_state->now.gift = *_state->spinner->target;
|
|
_state->now.backdrop = take(_state->spinnerBackdrops);
|
|
_state->now.pattern = take(_state->spinnerPatterns);
|
|
_state->now.model = take(_state->spinnerModels);
|
|
_state->now.forced = true;
|
|
_state->spinStarted = 0;
|
|
_state->spinner->state = SpinnerState::Finished;
|
|
}
|
|
}
|
|
|
|
void UniqueGiftCoverWidget::paintNormalAnimation(
|
|
QPainter &p,
|
|
const PaintContext &context,
|
|
float64 progress) {
|
|
if (progress < 1.) {
|
|
const auto finished = paintGift(p, _state->now, context, 1. - progress)
|
|
|| (_state->next.forced
|
|
&& (!_state->crossfading
|
|
|| !_state->crossfade.animating()));
|
|
const auto next = finished
|
|
? _state->next.model.lottie.get()
|
|
: nullptr;
|
|
if (next && next->ready()) {
|
|
_state->crossfading = true;
|
|
_state->updateAttributesPending = true;
|
|
_state->crossfade.start([this] {
|
|
update();
|
|
}, 0., 1., kCrossfadeDuration);
|
|
} else if (finished) {
|
|
if (const auto onstack = _state->checkSpinnerStart) {
|
|
onstack();
|
|
}
|
|
}
|
|
}
|
|
if (progress > 0.) {
|
|
p.setOpacity(progress);
|
|
paintGift(p, _state->next, context, progress);
|
|
}
|
|
}
|
|
|
|
void SetupResalePriceButton(
|
|
not_null<RpWidget*> parent,
|
|
rpl::producer<QColor> background,
|
|
rpl::producer<CreditsAmount> price,
|
|
Fn<void()> click) {
|
|
const auto resale = CreateChild<
|
|
FadeWrapScaled<AbstractButton>
|
|
>(parent, object_ptr<AbstractButton>(parent));
|
|
resale->move(0, 0);
|
|
|
|
const auto button = resale->entity();
|
|
const auto text = CreateChild<FlatLabel>(
|
|
button,
|
|
QString(),
|
|
st::uniqueGiftResalePrice);
|
|
text->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
text->sizeValue() | rpl::on_next([=](QSize size) {
|
|
const auto padding = st::uniqueGiftResalePadding;
|
|
const auto margin = st::uniqueGiftResaleMargin;
|
|
button->resize(size.grownBy(padding + margin));
|
|
text->move((margin + padding).left(), (margin + padding).top());
|
|
}, button->lifetime());
|
|
text->setTextColorOverride(QColor(255, 255, 255, 255));
|
|
|
|
std::move(price) | rpl::on_next([=](CreditsAmount value) {
|
|
if (value) {
|
|
text->setMarkedText(value.ton()
|
|
? Text::IconEmoji(&st::tonIconEmoji).append(
|
|
Lang::FormatCreditsAmountDecimal(value))
|
|
: Text::IconEmoji(&st::starIconEmoji).append(
|
|
Lang::FormatCountDecimal(value.whole())));
|
|
resale->toggle(true, anim::type::normal);
|
|
} else {
|
|
resale->toggle(false, anim::type::normal);
|
|
}
|
|
}, resale->lifetime());
|
|
resale->finishAnimating();
|
|
|
|
const auto bg = button->lifetime().make_state<rpl::variable<QColor>>(
|
|
std::move(background));
|
|
button->paintRequest() | rpl::on_next([=] {
|
|
auto p = QPainter(button);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
|
|
const auto inner = button->rect().marginsRemoved(
|
|
st::uniqueGiftResaleMargin);
|
|
const auto radius = inner.height() / 2.;
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(bg->current());
|
|
p.drawRoundedRect(inner, radius, radius);
|
|
}, button->lifetime());
|
|
bg->changes() | rpl::on_next([=] {
|
|
button->update();
|
|
}, button->lifetime());
|
|
|
|
if (click) {
|
|
resale->entity()->setClickedCallback(std::move(click));
|
|
} else {
|
|
resale->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
}
|
|
}
|
|
|
|
void GiftReleasedByHandler(not_null<PeerData*> peer) {
|
|
const auto session = &peer->session();
|
|
const auto window = session->tryResolveWindow(peer);
|
|
if (window) {
|
|
window->showPeerHistory(peer);
|
|
return;
|
|
}
|
|
const auto account = not_null(&session->account());
|
|
if (const auto window = Core::App().windowFor(account)) {
|
|
window->invokeForSessionController(
|
|
&session->account(),
|
|
peer,
|
|
[=](not_null<Window::SessionController*> window) {
|
|
window->showPeerHistory(peer);
|
|
});
|
|
}
|
|
}
|
|
|
|
object_ptr<UniqueGiftCoverWidget> MakeUniqueGiftCover(
|
|
QWidget *parent,
|
|
rpl::producer<UniqueGiftCover> data,
|
|
UniqueGiftCoverArgs &&args) {
|
|
return object_ptr<UniqueGiftCoverWidget>(
|
|
parent,
|
|
std::move(data),
|
|
std::move(args));
|
|
}
|
|
|
|
void AddUniqueGiftCover(
|
|
not_null<VerticalLayout*> container,
|
|
rpl::producer<UniqueGiftCover> data,
|
|
UniqueGiftCoverArgs &&args) {
|
|
container->add(
|
|
MakeUniqueGiftCover(container, std::move(data), std::move(args)));
|
|
}
|
|
|
|
} // namespace Ui
|