Support failure animation.

This commit is contained in:
John Preston
2026-01-23 20:03:54 +04:00
parent d556d3f575
commit 3f922f2333
9 changed files with 558 additions and 339 deletions
Binary file not shown.
+1
View File
@@ -4122,6 +4122,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_craft_progress" = "Crafting";
"lng_gift_craft_progress_chance" = "{percent} success chance";
"lng_gift_craft_progress_fails" = "If crafting fails, all selected gifts will be consumed.";
"lng_gift_craft_failed" = "Crafting failed. Gifts consumed :(";
"lng_auction_about_title" = "Auction";
"lng_auction_about_subtitle" = "Join the battle for exclusive gifts.";
@@ -55,6 +55,7 @@
<file alias="passkeys.tgs">../../animations/passkeys.tgs</file>
<file alias="ban.tgs">../../animations/ban.tgs</file>
<file alias="cocoon.tgs">../../animations/cocoon.tgs</file>
<file alias="bomb.tgs">../../animations/bomb.tgs</file>
<file alias="profile_muting.tgs">../../animations/profile/profile_muting.tgs</file>
<file alias="profile_unmuting.tgs">../../animations/profile/profile_unmuting.tgs</file>
File diff suppressed because it is too large Load Diff
@@ -13,6 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/radial_animation.h"
#include "ui/text/text_custom_emoji.h"
namespace Main {
class Session;
} // namespace Main
namespace HistoryView {
class StickerPlayer;
} // namespace HistoryView
namespace Info::PeerGifts {
class GiftButton;
} // namespace Info::PeerGifts
@@ -26,13 +34,11 @@ using CraftResultCallback = Fn<void(std::shared_ptr<Data::GiftUpgradeResult>)>;
struct BackdropView {
Data::UniqueGiftBackdrop colors;
QImage gradient;
};
struct PatternView {
std::unique_ptr<Text::CustomEmoji> emoji;
base::flat_map<int, base::flat_map<float64, QImage>> emojis;
};
struct CraftState {
@@ -84,6 +90,8 @@ struct CraftState {
std::array<EmptySide, 6> forgeSides;
EmptySide finalSide;
Main::Session *session = nullptr;
int containerHeight = 0;
int craftingTop = 0;
int craftingBottom = 0;
@@ -93,13 +101,22 @@ struct CraftState {
void paint(QPainter &p, QSize size, int craftingHeight, float64 slideProgress = 0.);
void updateForGiftCount(int count);
[[nodiscard]] EmptySide prepareEmptySide(int index) const;
};
struct FacePlacement {
int face = -1;
int rotation = 0;
};
struct FailureAnimation {
std::unique_ptr<HistoryView::StickerPlayer> player;
QImage frame;
DocumentData *document = nullptr;
Animations::Simple scaleAnimation;
bool scaleStarted = false;
bool playerStarted = false;
bool playerFinished = false;
rpl::lifetime lifetime;
};
struct CraftAnimationState {
@@ -115,10 +132,11 @@ struct CraftAnimationState {
int giftsLanded = 0;
int totalGifts = 0;
bool allGiftsLanded = false;
bool currentPhaseFinished = false;
std::array<FacePlacement, 4> giftToSide;
Animations::Simple flightAnimation;
int currentConfigIndex = -1;
int currentPhaseIndex = -1;
crl::time animationStartTime = 0;
float64 initialRotationX = 0.;
float64 initialRotationY = 0.;
@@ -134,11 +152,13 @@ struct CraftAnimationState {
crl::time loadingStartedTime = 0;
bool loadingFadingOut = false;
std::unique_ptr<FailureAnimation> failureAnimation;
};
void StartCraftAnimation(
not_null<VerticalLayout*> container,
std::shared_ptr<CraftState> state,
Fn<void(CraftResultCallback)> startRequest);
Fn<void(CraftResultCallback)> startRequest,
Fn<void()> closeOnFail);
} // namespace Ui
@@ -804,9 +804,10 @@ void ShowSelectGiftBox(
}
void Craft(
not_null<VerticalLayout*> container,
not_null<GenericBox*> box,
std::shared_ptr<CraftState> state,
const std::vector<GiftForCraft> &gifts) {
const std::vector<GiftForCraft> &gifts,
Fn<void()> closeParent) {
auto startRequest = [=](CraftResultCallback done) {
constexpr auto kDelays = std::array<crl::time, 7>{
100, 200, 300, 400, 500, 1000, 2000
@@ -814,10 +815,11 @@ void Craft(
const auto delay = kDelays[base::RandomIndex(kDelays.size())];
const auto giftsCopy = gifts;
base::call_delayed(delay, container, [=] {
const auto shouldSucceed = (base::RandomIndex(5) != 0);
if (shouldSucceed && !giftsCopy.empty()) {
const auto &chosen = giftsCopy[base::RandomIndex(giftsCopy.size())];
base::call_delayed(delay, box, [=] {
const auto shouldSucceed = true;// (base::RandomIndex(5) != 0);
const auto count = int(giftsCopy.size());
if (shouldSucceed && !count) {
const auto &chosen = giftsCopy[base::RandomIndex(count)];
auto info = Data::StarGift{
.id = chosen.unique->initialGiftId,
.unique = chosen.unique,
@@ -834,14 +836,31 @@ void Craft(
}
});
};
StartCraftAnimation(container, std::move(state), std::move(startRequest));
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,
std::move(state),
std::move(startRequest),
closeOnFail);
}
void MakeCraftContent(
not_null<GenericBox*> box,
not_null<Window::SessionController*> controller,
std::vector<GiftForCraft> gifts,
Fn<void()> closeParent,
bool autoStartCraft) {
Expects(!gifts.empty());
struct State {
std::shared_ptr<CraftState> craftState;
GradientButton *button = nullptr;
@@ -861,9 +880,11 @@ void MakeCraftContent(
bool crafting = false;
};
const auto session = &controller->session();
const auto initialGiftId = gifts.front().unique->initialGiftId;
const auto state = box->lifetime().make_state<State>();
state->craftState = std::make_shared<CraftState>();
state->craftState->session = session;
state->craftState->coversAnimate = true;
{
@@ -935,7 +956,6 @@ void MakeCraftContent(
}
}, raw->lifetime());
const auto initialGiftId = state->chosen.current().front().unique->initialGiftId;
std::move(
crafting.editRequests
) | rpl::on_next([=](int index) {
@@ -1071,7 +1091,7 @@ void MakeCraftContent(
const auto renderWidget = [&](QWidget *widget) {
const auto pos = widget->pos() - bottomRect.topLeft();
widget->render(&bottomPart, pos, QRegion(), QWidget::DrawChildren);
widget->render(&bottomPart, pos, {}, QWidget::DrawChildren);
};
renderWidget(state->about);
renderWidget(state->attributes);
@@ -1081,7 +1101,7 @@ void MakeCraftContent(
cs->bottomPartY = aboutPos.y();
cs->containerHeight = raw->height();
Craft(raw, state->craftState, state->chosen.current());
Craft(box, state->craftState, state->chosen.current(), closeParent);
};
button->setClickedCallback(startCrafting);
@@ -1113,12 +1133,18 @@ void MakeCraftContent(
void ShowGiftCraftBoxInternal(
not_null<Window::SessionController*> controller,
std::vector<GiftForCraft> gifts,
Fn<void()> closeParent,
bool autoStartCraft) {
controller->show(Box([=](not_null<GenericBox*> box) {
box->setStyle(st::giftCraftBox);
box->setWidth(st::boxWidth);
box->setNoContentMargin(true);
MakeCraftContent(box, controller, gifts, autoStartCraft);
MakeCraftContent(
box,
controller,
gifts,
closeParent,
autoStartCraft);
AddUniqueCloseButton(box);
#if _DEBUG
@@ -1132,7 +1158,7 @@ void ShowGiftCraftBoxInternal(
} else {
copy = full;
}
ShowGiftCraftBoxInternal(controller, copy, true);
ShowGiftCraftBoxInternal(controller, copy, [] {}, true);
});
}, box->lifetime());
}
@@ -1145,7 +1171,8 @@ void ShowGiftCraftBoxInternal(
void ShowGiftCraftInfoBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId) {
Data::SavedStarGiftId savedId,
Fn<void()> closeParent) {
controller->show(Box([=](not_null<GenericBox*> box) {
const auto container = box->verticalLayout();
auto cover = tr::lng_gift_craft_info_title(
@@ -1189,10 +1216,15 @@ void ShowGiftCraftInfoBox(
box->setNoContentMargin(true);
box->addButton(tr::lng_gift_craft_start_button(), [=] {
const auto weak = base::make_weak(box);
ShowGiftCraftBoxInternal(
controller,
{ GiftForCraft{ gift, savedId } },
closeParent,
false);
if (const auto strong = box.get()) {
strong->closeBox();
}
});
}));
}
@@ -1208,7 +1240,7 @@ void ShowTestGiftCraftBox(
entry.manageId = std::move(gift.manageId);
converted.push_back(std::move(entry));
}
ShowGiftCraftBoxInternal(controller, std::move(converted), true);
ShowGiftCraftBoxInternal(controller, std::move(converted), [] {}, true);
}
} // namespace Ui
@@ -27,7 +27,8 @@ struct GiftForCraftEntry {
void ShowGiftCraftInfoBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId);
Data::SavedStarGiftId savedId,
Fn<void()> closeParent);
void ShowTestGiftCraftBox(
not_null<Window::SessionController*> controller,
@@ -1579,7 +1579,11 @@ void GenericCreditsEntryBody(
const auto unique = e.uniqueGift;
const auto savedId = EntryToSavedStarGiftId(&show->session(), e);
if (const auto window = show->resolveWindow()) {
Ui::ShowGiftCraftInfoBox(window, unique, savedId);
const auto closeParent = crl::guard(box, [=] {
box->closeBox();
});
using namespace Ui;
ShowGiftCraftInfoBox(window, unique, savedId, closeParent);
}
} : Fn<void()>();
AddUniqueCloseMoreButton(box, st, [=](not_null<Ui::PopupMenu*> menu) {
@@ -1685,7 +1685,9 @@ SessionController::SessionController(
#if _DEBUG // TEST: Auto-open craft box on startup
constexpr auto kGiftsCount = 4;
crl::on_main(this, [=] {
return;
if (rand() % 2 == 0) {
return;
}
const auto user = session->user();
session->api().request(MTPpayments_GetSavedStarGifts(
MTP_flags(MTPpayments_GetSavedStarGifts::Flag::f_exclude_unlimited),