mirror of
https://github.com/Qz3rK/tdesktop.git
synced 2026-07-03 14:15:08 +02:00
Add loading state to craft animation starting.
This commit is contained in:
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/star_gift_craft_animation.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_common.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/top_background_gradient.h"
|
||||
@@ -23,6 +24,9 @@ using namespace Info::PeerGifts;
|
||||
|
||||
constexpr auto kFrameDuration = 1000. / 60.;
|
||||
constexpr auto kFlightDuration = crl::time(400);
|
||||
constexpr auto kLoadingFadeInDuration = crl::time(150);
|
||||
constexpr auto kLoadingFadeOutDuration = crl::time(300);
|
||||
constexpr auto kLoadingMinDuration = crl::time(600);
|
||||
|
||||
struct Rotation {
|
||||
float64 x = 0.;
|
||||
@@ -429,7 +433,8 @@ void PaintCubeFace(
|
||||
void PaintCubeFirstFlight(
|
||||
QPainter &p,
|
||||
const CraftAnimationState &animState,
|
||||
float64 progress) {
|
||||
float64 progress,
|
||||
bool skipForgeIcon = false) {
|
||||
const auto &shared = animState.shared;
|
||||
|
||||
const auto overlayBg = shared->forgeBgOverlay;
|
||||
@@ -446,7 +451,9 @@ void PaintCubeFirstFlight(
|
||||
p.setBrush(sideBg);
|
||||
p.drawRoundedRect(forge, radius, radius);
|
||||
|
||||
st::craftForge.paintInCenter(p, forge, st::white->c);
|
||||
if (!skipForgeIcon) {
|
||||
st::craftForge.paintInCenter(p, forge, st::white->c);
|
||||
}
|
||||
}
|
||||
|
||||
void PaintCube(
|
||||
@@ -872,7 +879,7 @@ void LandCurrentGift(CraftAnimationState *animState, crl::time now) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto giftNumber = animState->giftsLanded + 1;
|
||||
const auto giftNumber = ++animState->giftsLanded;
|
||||
const auto isLastGift = (giftNumber >= animState->totalGifts);
|
||||
|
||||
const auto configIndex = (giftNumber - 1) * 2 + (isLastGift ? 0 : 1);
|
||||
@@ -880,7 +887,6 @@ void LandCurrentGift(CraftAnimationState *animState, crl::time now) {
|
||||
|
||||
const auto &config = kGiftAnimations[configIndex];
|
||||
|
||||
++animState->giftsLanded;
|
||||
animState->currentlyFlying = -1;
|
||||
|
||||
animState->initialRotationX = animState->rotationX;
|
||||
@@ -891,6 +897,46 @@ void LandCurrentGift(CraftAnimationState *animState, crl::time now) {
|
||||
animState->nextFaceRotation = config.nextFaceRotation;
|
||||
}
|
||||
|
||||
void PaintLoadingAnimation(QPainter &p, CraftAnimationState *animState) {
|
||||
const auto &loading = animState->loadingAnimation;
|
||||
if (!loading || !animState->loadingStartedTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto loadingShown = animState->loadingShownAnimation.value(
|
||||
animState->loadingFadingOut ? 0. : 1.);
|
||||
if (loadingShown <= 0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto shared = animState->shared;
|
||||
const auto forge = shared->forgeRect.translated(0, shared->craftingOffsetY);
|
||||
const auto inner = forge.marginsRemoved({
|
||||
st::craftForgePadding,
|
||||
st::craftForgePadding,
|
||||
st::craftForgePadding,
|
||||
st::craftForgePadding,
|
||||
});
|
||||
|
||||
const auto state = loading->computeState();
|
||||
const auto adjustedState = RadialState{
|
||||
.shown = state.shown * loadingShown,
|
||||
.arcFrom = state.arcFrom,
|
||||
.arcLength = state.arcLength,
|
||||
};
|
||||
|
||||
auto pen = QPen(st::white->c);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
InfiniteRadialAnimation::Draw(
|
||||
p,
|
||||
adjustedState,
|
||||
inner.topLeft(),
|
||||
inner.size(),
|
||||
inner.width(),
|
||||
pen,
|
||||
st::craftForgeLoading.thickness);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QImage CraftState::CornerSnapshot::gift(float64 progress) const {
|
||||
@@ -1038,7 +1084,8 @@ QImage CraftState::prepareForgeImage(int index) const {
|
||||
|
||||
void StartCraftAnimation(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::shared_ptr<CraftState> state) {
|
||||
std::shared_ptr<CraftState> state,
|
||||
Fn<void(CraftResultCallback)> startRequest) {
|
||||
while (container->count() > 0) {
|
||||
delete container->widgetAt(0);
|
||||
}
|
||||
@@ -1058,6 +1105,10 @@ void StartCraftAnimation(
|
||||
}
|
||||
}
|
||||
|
||||
animState->loadingAnimation = std::make_unique<InfiniteRadialAnimation>(
|
||||
[=] { raw->update(); },
|
||||
st::craftForgeLoading);
|
||||
|
||||
raw->paintOn([=](QPainter &p) {
|
||||
const auto shared = animState->shared;
|
||||
const auto slideProgress = animState->slideOutAnimation.value(1.);
|
||||
@@ -1086,9 +1137,14 @@ void StartCraftAnimation(
|
||||
const auto flying = animState->currentlyFlying;
|
||||
const auto firstFlyProgress = (flying == 0)
|
||||
? animState->flightAnimation.value(1.)
|
||||
: 1.;
|
||||
: animState->giftsLanded
|
||||
? 1.
|
||||
: 0.;
|
||||
const auto loadingVisible = animState->loadingStartedTime
|
||||
&& !animState->loadingFadingOut;
|
||||
if (firstFlyProgress < 1.) {
|
||||
PaintCubeFirstFlight(p, *animState, firstFlyProgress);
|
||||
const auto skipForgeIcon = loadingVisible && anim::Disabled();
|
||||
PaintCubeFirstFlight(p, *animState, firstFlyProgress, skipForgeIcon);
|
||||
} else {
|
||||
PaintCube(p, *animState, cubeCenter, cubeSize);
|
||||
}
|
||||
@@ -1118,18 +1174,62 @@ void StartCraftAnimation(
|
||||
PaintFlyingGift(p, corner, position, progress);
|
||||
}
|
||||
}
|
||||
|
||||
PaintLoadingAnimation(p, animState);
|
||||
}
|
||||
});
|
||||
|
||||
const auto startFlying = [=] {
|
||||
if (animState->loadingStartedTime > 0) {
|
||||
animState->loadingFadingOut = true;
|
||||
animState->loadingShownAnimation.start(
|
||||
[=] { raw->update(); },
|
||||
1.,
|
||||
0.,
|
||||
kLoadingFadeOutDuration);
|
||||
}
|
||||
StartGiftFlight(animState, raw, 0);
|
||||
};
|
||||
|
||||
const auto tryStartFlying = [=] {
|
||||
const auto slideFinished = !animState->slideOutAnimation.animating();
|
||||
const auto resultArrived = animState->craftResult.has_value();
|
||||
const auto notYetFlying = (animState->currentlyFlying < 0)
|
||||
&& (animState->giftsLanded == 0);
|
||||
if (!slideFinished || !notYetFlying) {
|
||||
return;
|
||||
}
|
||||
if (!resultArrived) {
|
||||
if (!animState->loadingStartedTime) {
|
||||
animState->loadingStartedTime = crl::now();
|
||||
animState->loadingAnimation->start();
|
||||
animState->loadingShownAnimation.start(
|
||||
[=] { raw->update(); },
|
||||
0.,
|
||||
1.,
|
||||
kLoadingFadeInDuration);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!animState->loadingStartedTime) {
|
||||
startFlying();
|
||||
return;
|
||||
}
|
||||
const auto elapsed = crl::now() - animState->loadingStartedTime;
|
||||
if (elapsed >= kLoadingMinDuration) {
|
||||
startFlying();
|
||||
} else {
|
||||
base::call_delayed(kLoadingMinDuration - elapsed, raw, startFlying);
|
||||
}
|
||||
};
|
||||
|
||||
animState->slideOutAnimation.start(
|
||||
[=] {
|
||||
raw->update();
|
||||
|
||||
const auto progress = animState->slideOutAnimation.value(1.);
|
||||
if (progress >= 1.
|
||||
&& animState->currentlyFlying < 0
|
||||
&& animState->giftsLanded == 0) {
|
||||
StartGiftFlight(animState, raw, 0);
|
||||
if (progress >= 1.) {
|
||||
tryStartFlying();
|
||||
}
|
||||
},
|
||||
0.,
|
||||
@@ -1190,11 +1290,23 @@ void StartCraftAnimation(
|
||||
const auto hasMoreFlights = (animState->giftsLanded < animState->totalGifts);
|
||||
const auto isFlying = animState->flightAnimation.animating();
|
||||
const auto isAnimating = (animState->currentConfigIndex >= 0);
|
||||
const auto isLoadingShown = animState->loadingShownAnimation.animating();
|
||||
|
||||
return hasMoreFlights || isFlying || isAnimating;
|
||||
return hasMoreFlights || isFlying || isAnimating || isLoadingShown;
|
||||
});
|
||||
|
||||
container->add(std::move(canvas));
|
||||
|
||||
if (startRequest) {
|
||||
const auto weak = base::make_weak(raw);
|
||||
startRequest([=](std::shared_ptr<Data::GiftUpgradeResult> result) {
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
animState->craftResult = std::move(result);
|
||||
tryStartFlying();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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 Info::PeerGifts {
|
||||
@@ -20,6 +21,8 @@ namespace Ui {
|
||||
|
||||
class VerticalLayout;
|
||||
|
||||
using CraftResultCallback = Fn<void(std::shared_ptr<Data::GiftUpgradeResult>)>;
|
||||
|
||||
struct BackdropView {
|
||||
Data::UniqueGiftBackdrop colors;
|
||||
QImage gradient;
|
||||
@@ -118,10 +121,17 @@ struct CraftAnimationState {
|
||||
|
||||
std::optional<std::array<QPointF, 4>> flightTargetCorners;
|
||||
|
||||
std::optional<std::shared_ptr<Data::GiftUpgradeResult>> craftResult;
|
||||
std::unique_ptr<InfiniteRadialAnimation> loadingAnimation;
|
||||
Animations::Simple loadingShownAnimation;
|
||||
crl::time loadingStartedTime = 0;
|
||||
bool loadingFadingOut = false;
|
||||
|
||||
};
|
||||
|
||||
void StartCraftAnimation(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::shared_ptr<CraftState> state);
|
||||
std::shared_ptr<CraftState> state,
|
||||
Fn<void(CraftResultCallback)> startRequest);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/star_gift_craft_box.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/random.h"
|
||||
#include "boxes/star_gift_craft_animation.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
@@ -804,8 +805,36 @@ void ShowSelectGiftBox(
|
||||
|
||||
void Craft(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::shared_ptr<CraftState> state) {
|
||||
StartCraftAnimation(container, std::move(state));
|
||||
std::shared_ptr<CraftState> state,
|
||||
const std::vector<GiftForCraft> &gifts) {
|
||||
auto startRequest = [=](CraftResultCallback done) {
|
||||
constexpr auto kDelays = std::array<crl::time, 7>{
|
||||
100, 200, 300, 400, 500, 1000, 2000
|
||||
};
|
||||
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())];
|
||||
auto info = Data::StarGift{
|
||||
.id = chosen.unique->initialGiftId,
|
||||
.unique = chosen.unique,
|
||||
.document = chosen.unique->model.document,
|
||||
};
|
||||
auto result = std::make_shared<Data::GiftUpgradeResult>(
|
||||
Data::GiftUpgradeResult{
|
||||
.info = std::move(info),
|
||||
.manageId = chosen.manageId,
|
||||
});
|
||||
done(std::move(result));
|
||||
} else {
|
||||
done(nullptr);
|
||||
}
|
||||
});
|
||||
};
|
||||
StartCraftAnimation(container, std::move(state), std::move(startRequest));
|
||||
}
|
||||
|
||||
void MakeCraftContent(
|
||||
@@ -1052,7 +1081,7 @@ void MakeCraftContent(
|
||||
cs->bottomPartY = aboutPos.y();
|
||||
cs->containerHeight = raw->height();
|
||||
|
||||
Craft(raw, state->craftState);
|
||||
Craft(raw, state->craftState, state->chosen.current());
|
||||
};
|
||||
button->setClickedCallback(startCrafting);
|
||||
|
||||
|
||||
@@ -611,6 +611,10 @@ craftForgePercent: CraftRadialPercent {
|
||||
stroke: 4px;
|
||||
font: font(14px semibold);
|
||||
}
|
||||
craftForgeLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: windowFgActive;
|
||||
thickness: 4px;
|
||||
}
|
||||
craftPercentLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(11px semibold);
|
||||
|
||||
Reference in New Issue
Block a user