mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Implement craft success animation.
This commit is contained in:
@@ -337,6 +337,8 @@ PRIVATE
|
||||
boxes/star_gift_auction_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/star_gift_cover_box.cpp
|
||||
boxes/star_gift_cover_box.h
|
||||
boxes/star_gift_craft_animation.cpp
|
||||
boxes/star_gift_craft_animation.h
|
||||
boxes/star_gift_craft_box.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/star_gift_cover_box.h"
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -65,31 +66,6 @@ void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
struct UniqueGiftCoverArgs {
|
||||
rpl::producer<QString> pretitle;
|
||||
rpl::producer<TextWithEntities> subtitle;
|
||||
Fn<void()> subtitleClick;
|
||||
bool subtitleLinkColored = false;
|
||||
bool subtitleOutlined = false;
|
||||
rpl::producer<CreditsAmount> resalePrice;
|
||||
Fn<void()> resaleClick;
|
||||
bool attributesInfo = false;
|
||||
Fn<void(
|
||||
std::optional<Data::UniqueGift> now,
|
||||
std::optional<Data::UniqueGift> next,
|
||||
float64 progress)> repaintedHook;
|
||||
std::shared_ptr<Data::GiftUpgradeSpinner> upgradeSpinner;
|
||||
};
|
||||
struct UniqueGiftCover {
|
||||
Data::UniqueGift values;
|
||||
bool spinner = false;
|
||||
bool force = false;
|
||||
};
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args);
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
@@ -124,8 +100,6 @@ void ShowOfferBuyBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> unique);
|
||||
|
||||
void GiftReleasedByHandler(not_null<PeerData*> peer);
|
||||
|
||||
struct StarGiftUpgradeArgs {
|
||||
not_null<Window::SessionController*> controller;
|
||||
Data::StarGift stargift;
|
||||
@@ -196,4 +170,9 @@ void SendGiftBox(
|
||||
const Info::PeerGifts::GiftDescriptor &descriptor,
|
||||
rpl::producer<Data::GiftAuctionState> auctionState);
|
||||
|
||||
[[nodiscard]] Data::CreditsHistoryEntry EntryForUpgradedGift(
|
||||
const std::shared_ptr<Data::GiftUpgradeResult> &gift,
|
||||
uint64 nextToUpgradeStickerId = 0,
|
||||
Fn<void()> nextToUpgradeShow = nullptr);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
struct GiftUpgradeSpinner;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class VerticalLayout;
|
||||
|
||||
struct UniqueGiftCoverArgs {
|
||||
rpl::producer<QString> pretitle;
|
||||
rpl::producer<TextWithEntities> subtitle;
|
||||
Fn<void()> subtitleClick;
|
||||
bool subtitleLinkColored = false;
|
||||
bool subtitleOutlined = false;
|
||||
rpl::producer<CreditsAmount> resalePrice;
|
||||
Fn<void()> resaleClick;
|
||||
bool attributesInfo = false;
|
||||
Fn<void(
|
||||
std::optional<Data::UniqueGift> now,
|
||||
std::optional<Data::UniqueGift> next,
|
||||
float64 progress)> repaintedHook;
|
||||
std::shared_ptr<Data::GiftUpgradeSpinner> upgradeSpinner;
|
||||
};
|
||||
|
||||
struct UniqueGiftCover {
|
||||
Data::UniqueGift values;
|
||||
bool spinner = false;
|
||||
bool force = false;
|
||||
};
|
||||
|
||||
void SetupResalePriceButton(
|
||||
not_null<RpWidget*> parent,
|
||||
rpl::producer<QColor> background,
|
||||
rpl::producer<CreditsAmount> price,
|
||||
Fn<void()> click);
|
||||
|
||||
void GiftReleasedByHandler(not_null<PeerData*> peer);
|
||||
|
||||
class UniqueGiftCoverWidget final : public RpWidget {
|
||||
public:
|
||||
UniqueGiftCoverWidget(
|
||||
QWidget *parent,
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args);
|
||||
~UniqueGiftCoverWidget();
|
||||
|
||||
struct PaintContext {
|
||||
int width = 0;
|
||||
int patternAreaHeight = 0;
|
||||
};
|
||||
|
||||
struct CraftContext {
|
||||
QSize initial;
|
||||
QColor initialBg;
|
||||
float64 fadeInProgress = 0.;
|
||||
float64 expandProgress = 0.;
|
||||
};
|
||||
[[nodiscard]] QRect prepareCraftFrame(
|
||||
QImage &canvas,
|
||||
const CraftContext &context);
|
||||
|
||||
[[nodiscard]] QColor backgroundColor() const;
|
||||
[[nodiscard]] QColor foregroundColor() const;
|
||||
|
||||
private:
|
||||
struct BackdropView;
|
||||
struct PatternView;
|
||||
struct ModelView;
|
||||
struct GiftView;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
[[nodiscard]] QImage prepareBackdrop(
|
||||
BackdropView &backdrop,
|
||||
const PaintContext &context);
|
||||
void paintBackdrop(
|
||||
QPainter &p,
|
||||
BackdropView &backdrop,
|
||||
const PaintContext &context);
|
||||
void paintPattern(
|
||||
QPainter &p,
|
||||
PatternView &pattern,
|
||||
const BackdropView &backdrop,
|
||||
const PaintContext &context,
|
||||
float64 shown);
|
||||
bool paintModel(
|
||||
QPainter &p,
|
||||
ModelView &model,
|
||||
const PaintContext &context,
|
||||
float64 scale = 1.,
|
||||
bool paused = false);
|
||||
bool paintGift(
|
||||
QPainter &p,
|
||||
GiftView &gift,
|
||||
const PaintContext &context,
|
||||
float64 shown);
|
||||
|
||||
void paintSpinnerAnimation(QPainter &p, const PaintContext &context);
|
||||
void paintNormalAnimation(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
float64 progress);
|
||||
|
||||
struct State;
|
||||
const std::unique_ptr<State> _state;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<UniqueGiftCoverWidget> MakeUniqueGiftCover(
|
||||
QWidget *parent,
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args);
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/star_gift_craft_animation.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/star_gift_cover_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -16,10 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/peer_gifts/info_peer_gifts_common.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -38,6 +46,9 @@ constexpr auto kFailurePlayerStopFrame = 120;
|
||||
constexpr auto kFailurePlayerFps = 60;
|
||||
constexpr auto kFailurePlayerFadeOutFrames = 10;
|
||||
constexpr auto kFailureScaleDuration = crl::time(200);
|
||||
constexpr auto kSuccessFadeInDuration = crl::time(300);
|
||||
constexpr auto kSuccessExpandDuration = crl::time(400);
|
||||
constexpr auto kSuccessExpandStart = crl::time(100);
|
||||
|
||||
struct Rotation {
|
||||
float64 x = 0.;
|
||||
@@ -53,6 +64,10 @@ struct Rotation {
|
||||
|
||||
using RotationFn = Fn<Rotation(Rotation initial, crl::time t)>;
|
||||
|
||||
[[nodiscard]] int Slowing() {
|
||||
return anim::SlowMultiplier();
|
||||
}
|
||||
|
||||
[[nodiscard]] RotationFn DecayingRotation(Rotation impulse, float64 decay) {
|
||||
const auto lambda = -std::log(decay) / kFrameDuration;
|
||||
return [=](Rotation initial, crl::time t) {
|
||||
@@ -284,16 +299,22 @@ const auto kGiftAnimations = std::array<GiftAnimationConfig, 7>{{ {
|
||||
|
||||
void PaintCubeFace(
|
||||
QPainter &p,
|
||||
const QImage &face,
|
||||
const std::array<QPointF, 4> &corners,
|
||||
const QImage &face,
|
||||
QSize faceSize = QSize(),
|
||||
const QRect &source = QRect(),
|
||||
int rotation = 0) {
|
||||
if (face.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto w = face.width() / ratio;
|
||||
const auto h = face.height() / ratio;
|
||||
const auto origin = source.isEmpty() ? face.size() : source.size();
|
||||
if (faceSize.isEmpty()) {
|
||||
faceSize = origin / ratio;
|
||||
}
|
||||
const auto w = faceSize.width();
|
||||
const auto h = faceSize.height();
|
||||
|
||||
const auto srcRect = QPolygonF({
|
||||
QPointF(0, 0),
|
||||
@@ -328,7 +349,12 @@ void PaintCubeFace(
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.save();
|
||||
p.setTransform(transform, true);
|
||||
p.drawImage(QRectF(0, 0, w, h), face);
|
||||
const auto delta = source.isEmpty()
|
||||
? QPointF()
|
||||
: QPointF(
|
||||
((origin.width() / ratio) - faceSize.width()) / 2.,
|
||||
((origin.height() / ratio) - faceSize.height()) / 2.);
|
||||
p.drawImage(QRectF(-delta, QSizeF(origin) / ratio), face, source);
|
||||
p.restore();
|
||||
}
|
||||
|
||||
@@ -415,10 +441,10 @@ void PaintCubeFirstFlight(
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<FailureAnimation> SetupFailureAnimation(
|
||||
std::unique_ptr<CraftFailAnimation> SetupFailureAnimation(
|
||||
not_null<RpWidget*> canvas,
|
||||
not_null<CraftAnimationState*> state) {
|
||||
auto result = std::make_unique<FailureAnimation>();
|
||||
auto result = std::make_unique<CraftFailAnimation>();
|
||||
const auto raw = result.get();
|
||||
|
||||
const auto document
|
||||
@@ -460,21 +486,22 @@ std::unique_ptr<FailureAnimation> SetupFailureAnimation(
|
||||
|
||||
void FailureAnimationCheck(
|
||||
not_null<CraftAnimationState*> state,
|
||||
not_null<RpWidget*> canvas) {
|
||||
not_null<RpWidget*> canvas,
|
||||
crl::time now) {
|
||||
const auto animation = state->failureAnimation.get();
|
||||
if (!animation) {
|
||||
return;
|
||||
}
|
||||
const auto &config = kGiftAnimations[state->currentPhaseIndex];
|
||||
const auto elapsed = (crl::now() - state->animationStartTime);
|
||||
const auto failureAnimationDuration = (1000 * kFailurePlayerStopFrame)
|
||||
const auto elapsed = (now - state->animationStartTime);
|
||||
const auto failureDuration = (1000 * kFailurePlayerStopFrame)
|
||||
/ kFailurePlayerFps;
|
||||
if (!animation->playerStarted
|
||||
&& elapsed >= config.duration - failureAnimationDuration) {
|
||||
const auto playerStartTime = state->animationDuration - failureDuration;
|
||||
if (!animation->playerStarted && elapsed >= playerStartTime) {
|
||||
animation->playerStarted = true;
|
||||
}
|
||||
if (!animation->scaleStarted
|
||||
&& elapsed >= config.duration - kFailureScaleDuration) {
|
||||
const auto playerScaleStart = state->animationDuration
|
||||
- kFailureScaleDuration;
|
||||
if (!animation->scaleStarted && elapsed >= playerScaleStart) {
|
||||
animation->scaleStarted = true;
|
||||
animation->scaleAnimation.start([=] {
|
||||
canvas->update();
|
||||
@@ -537,6 +564,41 @@ void FailureAnimationCheck(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QRect PrepareCraftFrame(
|
||||
not_null<CraftAnimationState*> state,
|
||||
int faceIndex,
|
||||
crl::time now) {
|
||||
Expects(state->successAnimation != nullptr);
|
||||
|
||||
const auto &shared = state->shared.get();
|
||||
const auto success = state->successAnimation.get();
|
||||
const auto elapsed = now - state->animationStartTime;
|
||||
const auto end = state->animationDuration * Slowing();
|
||||
const auto left = end - elapsed;
|
||||
const auto fadeDuration = kSuccessFadeInDuration * Slowing();
|
||||
const auto fadeInProgress = std::clamp(
|
||||
(1. - (left / float64(fadeDuration))),
|
||||
0.,
|
||||
1.);
|
||||
const auto expandStart = kSuccessExpandStart * Slowing();
|
||||
const auto expanding = kSuccessExpandDuration * Slowing();
|
||||
const auto expandProgress = std::clamp(
|
||||
(expandStart - left) / float64(expanding),
|
||||
0.,
|
||||
1.);
|
||||
if (expandProgress == 1. && !success->finished) {
|
||||
success->finished = true;
|
||||
}
|
||||
const auto expanded = anim::easeOutCubic(1., expandProgress);
|
||||
success->expanded = expanded;
|
||||
return success->widget->prepareCraftFrame(success->frame, {
|
||||
.initial = shared->forgeRect.size(),
|
||||
.initialBg = shared->forgeSides[faceIndex].bg,
|
||||
.fadeInProgress = fadeInProgress,
|
||||
.expandProgress = expanded,
|
||||
});
|
||||
}
|
||||
|
||||
void PaintCube(
|
||||
QPainter &p,
|
||||
not_null<CraftAnimationState*> state,
|
||||
@@ -545,7 +607,8 @@ void PaintCube(
|
||||
float64 size) {
|
||||
const auto shared = state->shared.get();
|
||||
|
||||
FailureAnimationCheck(state, canvas);
|
||||
const auto now = crl::now();
|
||||
FailureAnimationCheck(state, canvas, now);
|
||||
|
||||
const auto totalScale = FailureAnimationScale(state);
|
||||
const auto showCube = (totalScale > 0.);
|
||||
@@ -565,13 +628,12 @@ void PaintCube(
|
||||
&& state->currentPhaseIndex >= 0) {
|
||||
const auto &config = kGiftAnimations[state->currentPhaseIndex];
|
||||
const auto elapsed = (crl::now() - state->animationStartTime);
|
||||
if (elapsed >= config.revealTime * anim::SlowMultiplier()) {
|
||||
if (elapsed >= config.revealTime * Slowing()) {
|
||||
state->nextFaceRevealed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto craftFailed = state->craftResult.has_value()
|
||||
&& !state->craftResult->get();
|
||||
const auto craftResult = state->successAnimation.get();
|
||||
|
||||
auto paintFailure = Fn<void()>();
|
||||
for (const auto faceIndex : faces) {
|
||||
@@ -587,7 +649,9 @@ void PaintCube(
|
||||
|
||||
const auto thisFaceRevealed = state->nextFaceRevealed
|
||||
&& (faceIndex == state->nextFaceIndex);
|
||||
auto faceSize = shared->forgeRect.size();
|
||||
auto faceImage = shared->forgeSides[faceIndex].frame;
|
||||
auto faceSource = QRect();
|
||||
auto faceRotation = thisFaceRevealed
|
||||
? state->nextFaceRotation
|
||||
: 0;
|
||||
@@ -596,6 +660,7 @@ void PaintCube(
|
||||
if (i != state->currentlyFlying
|
||||
&& state->giftToSide[i].face == faceIndex
|
||||
&& shared->corners[i].giftButton) {
|
||||
faceSize = QSize(); // Take from the frame.
|
||||
faceImage = shared->corners[i].gift(1.);
|
||||
faceRotation = state->giftToSide[i].rotation;
|
||||
break;
|
||||
@@ -603,7 +668,13 @@ void PaintCube(
|
||||
}
|
||||
|
||||
if (thisFaceRevealed && state->allGiftsLanded) {
|
||||
if (craftFailed) {
|
||||
const auto coverWidget = craftResult
|
||||
? craftResult->widget
|
||||
: nullptr;
|
||||
if (coverWidget) {
|
||||
faceSource = PrepareCraftFrame(state, faceIndex, now);
|
||||
faceImage = craftResult->frame;
|
||||
} else {
|
||||
const auto maybeOpacity = FailureAnimationPrepareFrame(
|
||||
state,
|
||||
canvas,
|
||||
@@ -616,8 +687,10 @@ void PaintCube(
|
||||
}
|
||||
PaintCubeFace(
|
||||
p,
|
||||
state->failureAnimation->frame,
|
||||
*corners,
|
||||
state->failureAnimation->frame,
|
||||
faceSize,
|
||||
faceSource,
|
||||
faceRotation);
|
||||
};
|
||||
}
|
||||
@@ -626,7 +699,13 @@ void PaintCube(
|
||||
}
|
||||
}
|
||||
if (showCube) {
|
||||
PaintCubeFace(p, faceImage, *corners, faceRotation);
|
||||
PaintCubeFace(
|
||||
p,
|
||||
*corners,
|
||||
faceImage,
|
||||
faceSize,
|
||||
faceSource,
|
||||
faceRotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,7 +863,7 @@ void PaintFlyingGiftWithQuad(
|
||||
}
|
||||
|
||||
const auto frame = corner.gift(progress);
|
||||
PaintCubeFace(p, frame, corners, 0);
|
||||
PaintCubeFace(p, corners, frame);
|
||||
}
|
||||
|
||||
void PaintFlyingGift(
|
||||
@@ -1054,6 +1133,7 @@ void LandCurrentGift(not_null<CraftAnimationState*> state, crl::time now) {
|
||||
state->currentPhaseIndex = configIndex;
|
||||
state->currentPhaseFinished = false;
|
||||
state->animationStartTime = now;
|
||||
state->animationDuration = config.duration;
|
||||
state->nextFaceIndex = config.targetFaceIndex;
|
||||
state->nextFaceRotation = config.targetFaceRotation;
|
||||
state->nextFaceRevealed = false;
|
||||
@@ -1211,7 +1291,7 @@ void CraftState::paint(
|
||||
}
|
||||
}
|
||||
|
||||
void CraftState::updateForGiftCount(int count) {
|
||||
void CraftState::updateForGiftCount(int count, Fn<void()> repaint) {
|
||||
for (auto i = 4; i != 0;) {
|
||||
auto &cover = covers[--i];
|
||||
const auto shown = (i < count);
|
||||
@@ -1219,11 +1299,11 @@ void CraftState::updateForGiftCount(int count) {
|
||||
cover.shown = shown;
|
||||
const auto from = shown ? 0. : 1.;
|
||||
const auto to = shown ? 1. : 0.;
|
||||
cover.shownAnimation.start([this] {
|
||||
if (repaint) {
|
||||
repaint();
|
||||
}
|
||||
}, from, to, st::fadeWrapDuration);
|
||||
cover.shownAnimation.start(
|
||||
repaint,
|
||||
from,
|
||||
to,
|
||||
st::fadeWrapDuration);
|
||||
coversAnimate = true;
|
||||
}
|
||||
}
|
||||
@@ -1256,10 +1336,20 @@ CraftState::EmptySide CraftState::prepareEmptySide(int index) const {
|
||||
}
|
||||
|
||||
void StartCraftAnimation(
|
||||
not_null<VerticalLayout*> container,
|
||||
not_null<GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<CraftState> shared,
|
||||
Fn<void(CraftResultCallback)> startRequest,
|
||||
Fn<void()> closeOnFail) {
|
||||
Fn<void()> closeParent) {
|
||||
const auto container = box->verticalLayout();
|
||||
const auto closeOnFail = crl::guard(box, [=] {
|
||||
if (const auto onstack = closeParent) {
|
||||
onstack();
|
||||
}
|
||||
box->closeBox();
|
||||
show->showToast("failed");// tr::lng_gift_craft_failed(tr::now));
|
||||
});
|
||||
|
||||
while (container->count() > 0) {
|
||||
delete container->widgetAt(0);
|
||||
}
|
||||
@@ -1270,8 +1360,10 @@ void StartCraftAnimation(
|
||||
const auto height = shared->containerHeight;
|
||||
const auto craftingHeight = shared->craftingBottom - shared->craftingTop;
|
||||
state->shared = std::move(shared);
|
||||
state->shared->craftingStarted = true;
|
||||
|
||||
raw->resize(container->width(), height);
|
||||
const auto craftingSize = QSize(container->width(), height);
|
||||
raw->resize(craftingSize);
|
||||
|
||||
for (auto &corner : state->shared->corners) {
|
||||
if (corner.giftButton) {
|
||||
@@ -1290,15 +1382,41 @@ void StartCraftAnimation(
|
||||
}
|
||||
|
||||
const auto shared = state->shared.get();
|
||||
const auto success = state->successAnimation.get();
|
||||
const auto slideProgress = state->slideOutAnimation.value(1.);
|
||||
shared->paint(p, raw->size(), craftingHeight, slideProgress);
|
||||
if (slideProgress < 1.) {
|
||||
shared->paint(p, craftingSize, craftingHeight, slideProgress);
|
||||
} else {
|
||||
if (shared->craftBg.isNull()) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
shared->craftBg = QImage(
|
||||
craftingSize * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
shared->craftBg.setDevicePixelRatio(ratio);
|
||||
shared->craftBg.fill(Qt::transparent);
|
||||
|
||||
auto q = QPainter(&shared->craftBg);
|
||||
shared->paint(q, craftingSize, craftingHeight, 1.);
|
||||
}
|
||||
if (success && success->hiding) {
|
||||
p.setOpacity(1. - success->heightAnimation.value(1.));
|
||||
}
|
||||
p.drawImage(0, 0, shared->craftBg);
|
||||
if (success && success->hiding) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
}
|
||||
|
||||
shared->craftingOffsetY = (shared->containerHeight / 2.)
|
||||
- shared->craftingAreaCenterY;
|
||||
if (slideProgress < 1.) {
|
||||
shared->craftingOffsetY *= slideProgress;
|
||||
PaintSlideOutPhase(p, shared, raw->size(), slideProgress);
|
||||
PaintSlideOutPhase(p, shared, craftingSize, slideProgress);
|
||||
} else {
|
||||
if (success) {
|
||||
const auto target = success->coverShift;
|
||||
shared->craftingOffsetY -= target * success->expanded;
|
||||
}
|
||||
const auto cubeSize = float64(shared->forgeRect.width());
|
||||
const auto cubeCenter = QPointF(shared->forgeRect.topLeft())
|
||||
+ QPointF(cubeSize, cubeSize) / 2.
|
||||
@@ -1426,13 +1544,14 @@ void StartCraftAnimation(
|
||||
state->continuousAnimation.init([=](crl::time now) {
|
||||
if (state->currentPhaseIndex >= 0) {
|
||||
const auto &config = kGiftAnimations[state->currentPhaseIndex];
|
||||
const auto elapsed = (now - state->animationStartTime)
|
||||
/ anim::SlowMultiplier();
|
||||
const auto elapsed = state->currentPhaseFinished
|
||||
? (config.duration * Slowing())
|
||||
: (now - state->animationStartTime);
|
||||
const auto initial = Rotation{
|
||||
state->initialRotationX,
|
||||
state->initialRotationY,
|
||||
};
|
||||
const auto r = config.rotation(initial, elapsed);
|
||||
const auto r = config.rotation(initial, elapsed / Slowing());
|
||||
state->rotationX = r.x;
|
||||
state->rotationY = r.y;
|
||||
}
|
||||
@@ -1449,42 +1568,67 @@ void StartCraftAnimation(
|
||||
&& state->giftsLanded > 0
|
||||
&& state->giftsLanded < state->totalGifts
|
||||
&& state->currentPhaseIndex >= 0) {
|
||||
|
||||
const auto &config = kGiftAnimations[state->currentPhaseIndex];
|
||||
const auto elapsed = (now - state->animationStartTime)
|
||||
/ anim::SlowMultiplier();
|
||||
const auto flightStartTime = config.duration - kFlightDuration;
|
||||
if (elapsed >= flightStartTime) {
|
||||
const auto elapsed = now - state->animationStartTime;
|
||||
const auto start = state->animationDuration - kFlightDuration;
|
||||
if (elapsed >= start * Slowing()) {
|
||||
StartGiftFlightToFace(state, raw, state->nextFaceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const auto success = state->successAnimation.get();
|
||||
if (!state->currentPhaseFinished
|
||||
&& !state->flightAnimation.animating()
|
||||
&& state->currentlyFlying < 0
|
||||
&& state->giftsLanded >= state->totalGifts
|
||||
&& state->totalGifts > 0
|
||||
&& state->currentPhaseIndex >= 0) {
|
||||
|
||||
const auto &config = kGiftAnimations[state->currentPhaseIndex];
|
||||
const auto elapsed = (now - state->animationStartTime)
|
||||
/ anim::SlowMultiplier();
|
||||
if (elapsed >= config.duration) {
|
||||
const auto elapsed = now - state->animationStartTime;
|
||||
if (elapsed >= state->animationDuration * Slowing()) {
|
||||
state->currentPhaseFinished = true;
|
||||
if (success) {
|
||||
const auto wasContent = box->height();
|
||||
const auto wasTotal = box->parentWidget()->height();
|
||||
const auto wasButtons = wasTotal - wasContent;
|
||||
|
||||
container->clear();
|
||||
container->add(std::move(success->owned));
|
||||
|
||||
box->setStyle(st::giftBox);
|
||||
|
||||
const auto entry = EntryForUpgradedGift(
|
||||
*state->craftResult);
|
||||
Settings::GenericCreditsEntryBody(box, show, entry);
|
||||
container->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
const auto nowContent = box->height();
|
||||
const auto nowTotal = box->parentWidget()->height();
|
||||
const auto nowButtons = nowTotal - nowContent;
|
||||
|
||||
box->animateHeightFrom(
|
||||
wasContent + wasButtons - nowButtons);
|
||||
success->hiding = true;
|
||||
const auto duration = kSuccessExpandDuration
|
||||
- kSuccessExpandStart;
|
||||
success->heightAnimation.start([=] {
|
||||
const auto height = anim::interpolate(
|
||||
craftingSize.height(),
|
||||
success->coverHeight,
|
||||
success->heightAnimation.value(1.));
|
||||
raw->resize(craftingSize.width(), height);
|
||||
}, 0., 1., duration, anim::easeOutCubic);
|
||||
|
||||
raw->setParent(box->parentWidget());
|
||||
raw->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (success->finished) {
|
||||
state->continuousAnimation.stop();
|
||||
raw->hide();
|
||||
|
||||
const auto hasMoreFlights = (state->giftsLanded < state->totalGifts);
|
||||
const auto isFlying = state->flightAnimation.animating();
|
||||
const auto isAnimating = !state->currentPhaseFinished;
|
||||
const auto isLoadingShown = state->loadingShownAnimation.animating();
|
||||
const auto isFailureAnimation = state->failureAnimation != nullptr;
|
||||
|
||||
return hasMoreFlights
|
||||
|| isFlying
|
||||
|| isAnimating
|
||||
|| isLoadingShown
|
||||
|| isFailureAnimation;
|
||||
StartFireworks(box->parentWidget());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
container->add(std::move(canvas));
|
||||
@@ -1496,6 +1640,27 @@ void StartCraftAnimation(
|
||||
return;
|
||||
}
|
||||
state->craftResult = std::move(result);
|
||||
if (const auto result = state->craftResult->get()) {
|
||||
state->successAnimation = std::make_unique<
|
||||
CraftDoneAnimation
|
||||
>(CraftDoneAnimation{
|
||||
.owned = MakeUniqueGiftCover(
|
||||
container,
|
||||
rpl::single(UniqueGiftCover{ *result->info.unique }),
|
||||
{}),
|
||||
});
|
||||
const auto success = state->successAnimation.get();
|
||||
|
||||
const auto raw = success->owned.get();
|
||||
success->widget = raw;
|
||||
raw->hide();
|
||||
raw->resizeToWidth(st::boxWideWidth);
|
||||
SendPendingMoveResizeEvents(raw);
|
||||
|
||||
const auto fullHeight = state->shared->containerHeight;
|
||||
success->coverHeight = raw->height();
|
||||
success->coverShift = (fullHeight - raw->height()) / 2;
|
||||
}
|
||||
tryStartFlying();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,12 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -27,7 +32,9 @@ class GiftButton;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
class UniqueGiftCoverWidget;
|
||||
|
||||
using CraftResultCallback = Fn<void(std::shared_ptr<Data::GiftUpgradeResult>)>;
|
||||
|
||||
@@ -56,7 +63,8 @@ struct CraftState {
|
||||
QColor button1;
|
||||
QColor button2;
|
||||
bool coversAnimate = false;
|
||||
Fn<void()> repaint;
|
||||
bool craftingStarted = false;
|
||||
QImage craftBg;
|
||||
|
||||
QImage topPart;
|
||||
QRect topPartRect;
|
||||
@@ -98,8 +106,12 @@ struct CraftState {
|
||||
int craftingAreaCenterY = 0;
|
||||
int craftingOffsetY = 0;
|
||||
|
||||
void paint(QPainter &p, QSize size, int craftingHeight, float64 slideProgress = 0.);
|
||||
void updateForGiftCount(int count);
|
||||
void paint(
|
||||
QPainter &p,
|
||||
QSize size,
|
||||
int craftingHeight,
|
||||
float64 slideProgress = 0.);
|
||||
void updateForGiftCount(int count, Fn<void()> repaint);
|
||||
[[nodiscard]] EmptySide prepareEmptySide(int index) const;
|
||||
};
|
||||
|
||||
@@ -108,7 +120,20 @@ struct FacePlacement {
|
||||
int rotation = 0;
|
||||
};
|
||||
|
||||
struct FailureAnimation {
|
||||
struct CraftDoneAnimation {
|
||||
object_ptr<UniqueGiftCoverWidget> owned;
|
||||
UniqueGiftCoverWidget *widget = nullptr;
|
||||
Animations::Simple shownAnimation;
|
||||
Animations::Simple heightAnimation;
|
||||
QImage frame;
|
||||
int coverHeight = 0;
|
||||
int coverShift = 0;
|
||||
float64 expanded = 0.;
|
||||
bool finished = false;
|
||||
bool hiding = false;
|
||||
};
|
||||
|
||||
struct CraftFailAnimation {
|
||||
std::unique_ptr<HistoryView::StickerPlayer> player;
|
||||
QImage frame;
|
||||
DocumentData *document = nullptr;
|
||||
@@ -138,6 +163,7 @@ struct CraftAnimationState {
|
||||
|
||||
int currentPhaseIndex = -1;
|
||||
crl::time animationStartTime = 0;
|
||||
crl::time animationDuration = 0;
|
||||
float64 initialRotationX = 0.;
|
||||
float64 initialRotationY = 0.;
|
||||
int nextFaceIndex = 0;
|
||||
@@ -152,13 +178,15 @@ struct CraftAnimationState {
|
||||
crl::time loadingStartedTime = 0;
|
||||
bool loadingFadingOut = false;
|
||||
|
||||
std::unique_ptr<FailureAnimation> failureAnimation;
|
||||
std::unique_ptr<CraftDoneAnimation> successAnimation;
|
||||
std::unique_ptr<CraftFailAnimation> failureAnimation;
|
||||
};
|
||||
|
||||
void StartCraftAnimation(
|
||||
not_null<VerticalLayout*> container,
|
||||
not_null<GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<CraftState> state,
|
||||
Fn<void(CraftResultCallback)> startRequest,
|
||||
Fn<void()> closeOnFail);
|
||||
Fn<void()> closeParent);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -292,7 +292,7 @@ AbstractButton *MakeRemoveButton(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<std::vector<GiftForCraft>> chosen,
|
||||
rpl::producer<QColor> edgeColor) {
|
||||
const auto width = st::boxWidth;
|
||||
const auto width = st::boxWideWidth;
|
||||
|
||||
const auto buttonPadding = st::craftPreviewPadding;
|
||||
const auto buttonSize = st::giftBoxGiftTiny;
|
||||
@@ -805,6 +805,7 @@ void ShowSelectGiftBox(
|
||||
|
||||
void Craft(
|
||||
not_null<GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<CraftState> state,
|
||||
const std::vector<GiftForCraft> &gifts,
|
||||
Fn<void()> closeParent) {
|
||||
@@ -818,7 +819,7 @@ void Craft(
|
||||
base::call_delayed(delay, box, [=] {
|
||||
const auto shouldSucceed = true;// (base::RandomIndex(5) != 0);
|
||||
const auto count = int(giftsCopy.size());
|
||||
if (shouldSucceed && !count) {
|
||||
if (shouldSucceed && count > 0) {
|
||||
const auto &chosen = giftsCopy[base::RandomIndex(count)];
|
||||
auto info = Data::StarGift{
|
||||
.id = chosen.unique->initialGiftId,
|
||||
@@ -836,21 +837,12 @@ void Craft(
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
const auto show = box->uiShow();
|
||||
const auto closeOnFail = crl::guard(box, [=] {
|
||||
if (const auto onstack = closeParent) {
|
||||
onstack();
|
||||
}
|
||||
box->closeBox();
|
||||
show->showToast(tr::lng_gift_craft_failed(tr::now));
|
||||
});
|
||||
StartCraftAnimation(
|
||||
container,
|
||||
box,
|
||||
std::move(show),
|
||||
std::move(state),
|
||||
std::move(startRequest),
|
||||
closeOnFail);
|
||||
closeParent);
|
||||
}
|
||||
|
||||
void MakeCraftContent(
|
||||
@@ -924,10 +916,11 @@ void MakeCraftContent(
|
||||
|
||||
const auto raw = box->verticalLayout();
|
||||
|
||||
state->craftState->repaint = [=] { raw->update(); };
|
||||
state->chosen.value(
|
||||
) | rpl::on_next([=](const std::vector<GiftForCraft> &gifts) {
|
||||
state->craftState->updateForGiftCount(int(gifts.size()));
|
||||
state->craftState->updateForGiftCount(int(gifts.size()), [=] {
|
||||
raw->update();
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
const auto title = state->title = raw->add(
|
||||
@@ -1031,6 +1024,9 @@ void MakeCraftContent(
|
||||
|
||||
raw->paintOn([=](QPainter &p) {
|
||||
const auto &cs = state->craftState;
|
||||
if (cs->craftingStarted) {
|
||||
return;
|
||||
}
|
||||
const auto wasButton1 = cs->button1;
|
||||
const auto wasButton2 = cs->button2;
|
||||
cs->paint(p, raw->size(), craftingHeight);
|
||||
@@ -1101,7 +1097,12 @@ void MakeCraftContent(
|
||||
cs->bottomPartY = aboutPos.y();
|
||||
cs->containerHeight = raw->height();
|
||||
|
||||
Craft(box, state->craftState, state->chosen.current(), closeParent);
|
||||
Craft(
|
||||
box,
|
||||
controller->uiShow(),
|
||||
state->craftState,
|
||||
state->chosen.current(),
|
||||
closeParent);
|
||||
};
|
||||
button->setClickedCallback(startCrafting);
|
||||
|
||||
@@ -1137,7 +1138,7 @@ void ShowGiftCraftBoxInternal(
|
||||
bool autoStartCraft) {
|
||||
controller->show(Box([=](not_null<GenericBox*> box) {
|
||||
box->setStyle(st::giftCraftBox);
|
||||
box->setWidth(st::boxWidth);
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setNoContentMargin(true);
|
||||
MakeCraftContent(
|
||||
box,
|
||||
|
||||
@@ -1488,6 +1488,13 @@ void GenericCreditsEntryBox(
|
||||
GenericCreditsEntryBody(box, show, e, s, nullptr, st);
|
||||
}
|
||||
|
||||
void GenericCreditsEntryBody(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::CreditsHistoryEntry &e) {
|
||||
GenericCreditsEntryBody(box, std::move(show), e, {}, nullptr, {});
|
||||
}
|
||||
|
||||
void GenericCreditsEntryBody(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
||||
@@ -152,6 +152,10 @@ void GenericCreditsEntryBox(
|
||||
const Data::CreditsHistoryEntry &e,
|
||||
const Data::SubscriptionEntry &s,
|
||||
CreditsEntryBoxStyleOverrides st = {});
|
||||
void GenericCreditsEntryBody(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::CreditsHistoryEntry &e);
|
||||
void GenericCreditsEntryBody(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
||||
@@ -604,7 +604,7 @@ craftPattern: IconEmoji {
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
craftAddIcon: icon {{ "settings/plus", windowFgActive }};
|
||||
craftPreviewPadding: margins(24px, 12px, 18px, 8px);
|
||||
craftPreviewPadding: margins(24px, 12px, 40px, 8px);
|
||||
craftForge: icon{{ "chat/filled_forge-48x48", windowFg }};
|
||||
craftForgePadding: 12px;
|
||||
craftForgePercent: CraftRadialPercent {
|
||||
|
||||
@@ -1683,9 +1683,9 @@ SessionController::SessionController(
|
||||
});
|
||||
|
||||
#if _DEBUG // TEST: Auto-open craft box on startup
|
||||
constexpr auto kGiftsCount = 4;
|
||||
constexpr auto kGiftsCount = 1;
|
||||
crl::on_main(this, [=] {
|
||||
if (rand() % 2 == 0) {
|
||||
if (rand() % 2 < 0) {
|
||||
return;
|
||||
}
|
||||
const auto user = session->user();
|
||||
|
||||
Reference in New Issue
Block a user