Mostly ready emoji stake edit box.

This commit is contained in:
John Preston
2025-12-16 20:42:03 +04:00
parent 4a7148e20a
commit c8d5817942
12 changed files with 235 additions and 44 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -2477,7 +2477,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stake_game_beta" = "Beta";
"lng_stake_game_about" = "A limited play-test of the upcoming emoji mini-game platform for a small group of users.";
"lng_stake_game_results" = "Results and Returns";
"lng_stake_game_resets" = "A streak resets after 3 sixes or a stake change.";
"lng_stake_game_resets" = "A streak resets after 3 {emoji} or a stake change.";
"lng_stake_game_your" = "Your stake";
"lng_stake_game_save_and_roll" = "Save and Roll";
@@ -67,6 +67,13 @@
<file alias="slot_pull.tgs">../../animations/dice/slot_pull.tgs</file>
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
<file alias="dice_1.tgs">../../animations/dice/dice_1.tgs</file>
<file alias="dice_2.tgs">../../animations/dice/dice_2.tgs</file>
<file alias="dice_3.tgs">../../animations/dice/dice_3.tgs</file>
<file alias="dice_4.tgs">../../animations/dice/dice_4.tgs</file>
<file alias="dice_5.tgs">../../animations/dice/dice_5.tgs</file>
<file alias="dice_6.tgs">../../animations/dice/dice_6.tgs</file>
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
<file alias="star_reaction_center.tgs">../../animations/star_reaction/center.tgs</file>
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
@@ -47,6 +47,7 @@ Dice::Dice(not_null<Element*> parent, not_null<Data::MediaDice*> dice)
if (const auto outcome = _dice->outcome()) {
_outcomeSet = true;
_outcomeValue = _dice->value();
_outcomeNanoTon = outcome.nanoTon;
updateOutcomeMessage();
}
@@ -75,7 +76,7 @@ void Dice::updateOutcomeMessage() {
lt_amount,
amount,
tr::marked))
: !_dice->value()
: !_outcomeValue
? tr::lng_action_stake_game_loading(tr::now, tr::marked)
: (out
? tr::lng_action_stake_game_nothing_you(tr::now, tr::marked)
@@ -103,11 +104,15 @@ bool Dice::updateItemData() {
const auto outcome = _dice->outcome();
const auto outcomeSet = !!outcome;
const auto outcomeNanoTon = outcomeSet ? outcome.nanoTon : 0;
if (_outcomeSet == outcomeSet && _outcomeNanoTon == outcomeNanoTon) {
const auto outcomeValue = _dice->value();
if (_outcomeSet == outcomeSet
&& _outcomeNanoTon == outcomeNanoTon
&& _outcomeValue == outcomeValue) {
return false;
}
_outcomeSet = outcomeSet;
_outcomeNanoTon = outcomeNanoTon;
_outcomeValue = outcomeValue;
updateOutcomeMessage();
return true;
}
@@ -53,6 +53,7 @@ private:
std::optional<Sticker> _start;
std::optional<Sticker> _end;
int64 _outcomeNanoTon = false;
int _outcomeValue = 0;
mutable bool _showLastFrame = false;
mutable bool _drawingEnd = false;
bool _outcomeSet = false;
+217 -39
View File
@@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/emoji_stake_box.h"
#include "base/object_ptr.h"
#include "chat_helpers/stickers_lottie.h"
#include "data/components/credits.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
@@ -18,12 +22,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/ton_common.h"
#include "ui/text/custom_emoji_helper.h"
#include "ui/text/custom_emoji_text_badge.h"
#include "ui/text/text_lottie_custom_emoji.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "styles/style_calls.h" // confcallJoinBox
#include "styles/style_chat.h"
@@ -53,15 +59,140 @@ namespace {
const auto &size = st::settingsCloudPasswordIconSize;
auto icon = Settings::CreateLottieIcon(
box->verticalLayout(),
{ .name = u"dice_idle"_q, .sizeOverride = { size, size } },
{ .name = u"dice_6"_q, .sizeOverride = { size, size } },
st::settingLocalPasscodeIconPadding);
const auto animate = std::move(icon.animate);
box->showFinishes() | rpl::take(1) | rpl::on_next([=] {
animate(anim::repeat::loop);
animate(anim::repeat::once);
}, box->lifetime());
return std::move(icon.widget);
}
[[nodiscard]] object_ptr<RpWidget> MakeTable(
not_null<RpWidget*> parent,
const EmojiGameStakeArgs &args) {
auto result = object_ptr<RpWidget>(parent);
const auto raw = result.data();
struct State {
std::array<std::unique_ptr<Text::CustomEmoji>, 6> dices;
std::array<Text::String, 7> multiplicators;
};
const auto state = raw->lifetime().make_state<State>();
const auto serialize = [&](int milliReward) {
return QString(QChar(0xD7)) + QString::number(milliReward / 1000.);
};
const auto esize = st::largeEmojiSize;
for (auto i = 0; i != 6; ++i) {
state->dices[i] = Lottie::MakeEmoji({
.name = u"dice_%1"_q.arg(i + 1),
.sizeOverride = QSize(esize, esize),
}, [=] { raw->update(); });
const auto value = (i < args.milliRewards.size())
? args.milliRewards[i]
: 0;
state->multiplicators[i].setText(
st::semiboldTextStyle,
serialize(value));
}
state->multiplicators[6].setText(
st::semiboldTextStyle,
serialize(args.jackpotMilliReward));
raw->paintOn([=](QPainter &p) {
auto path = QPainterPath();
const auto &st = st::defaultTable;
const auto border = st.border;
const auto half = border / 2.;
const auto add = QMargins(border, border, border, border);
const auto inner = raw->rect().marginsRemoved(add);
path.addRoundedRect(inner, st.radius, st.radius);
{
const auto y = border + inner.height() / 2.;
path.moveTo(border + half, y);
path.lineTo(border + inner.width() - half, y);
}
{
const auto x = border + inner.width() / 2.;
path.moveTo(x, border + half);
path.lineTo(x, border + inner.height() - half);
}
{
const auto x = border + (inner.width() - border) / 4.;
path.moveTo(x, border + half);
path.lineTo(x, border + inner.height() - half);
}
{
const auto x = border
+ (inner.width() / 2.)
+ (inner.width() - border) / 4.;
path.moveTo(x, border + half);
path.lineTo(x, border + (inner.height() / 2.) - half);
}
auto pen = st.borderFg->p;
pen.setWidth(st.border);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
auto hq = PainterHighQualityEnabler(p);
p.drawPath(path);
const auto etop = -style::ConvertScale(10);
const auto ttop = etop + esize;
{
auto left = border + half;
const auto width = (inner.width() - 3 * border) / 4.;
auto top = border + half;
const auto now = crl::now();
for (auto i = 0; i != 7; ++i, left += width + border) {
if (i == 4) {
left -= 4 * (width + border);
top += border + half + inner.height() / 2.;
}
const auto x = int(base::SafeRound(left));
const auto y = int(base::SafeRound(top));
const auto right = (i < 6)
? int(base::SafeRound(left + width))
: int(base::SafeRound(left + 2 * width + border));
const auto w = (right - x);
if (i < 6) {
state->dices[i]->paint(p, {
.textColor = st::windowBoldFg->c,
.now = now,
.position = QPoint(x + (w - esize) / 2, y + etop),
.paused = true,
.internal = { .forceLastFrame = true }
});
} else {
const auto e = state->dices.back().get();
for (auto j = 0; j != 3; ++j) {
e->paint(p, {
.textColor = st::windowBoldFg->c,
.now = now,
.position = QPoint(
x + (w - 3 * esize) / 2 + (esize * j),
y + etop),
.paused = true,
.internal = { .forceLastFrame = true }
});
}
}
p.setPen(st::windowBoldFg);
const auto &mul = state->multiplicators[i];
mul.draw(p, {
.position = { x + (w - mul.maxWidth()) / 2, y + ttop },
});
}
}
});
raw->widthValue() | rpl::on_next([=](int width) {
raw->resize(width, width / 3);
}, raw->lifetime());
return result;
}
} // namespace
not_null<NumberInput*> AddStarsInputField(
@@ -124,10 +255,12 @@ void EmojiGameStakeBox(
box->setStyle(st::confcallJoinBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
const auto container = box->verticalLayout();
const auto skip = st::confcallLinkHeaderIconPadding.bottom();
box->addRow(
MakeLogo(box),
st::boxRowPadding + st::confcallLinkHeaderIconPadding,
st::boxRowPadding + QMargins(0, 0, 0, skip),
style::al_top);
auto helper = Text::CustomEmojiHelper();
@@ -154,13 +287,91 @@ void EmojiGameStakeBox(
box,
tr::lng_stake_game_about(tr::rich),
st::confcallLinkCenteredText),
st::boxRowPadding,
st::boxRowPadding + QMargins(0, 0, 0, skip),
style::al_top
)->setTryMakeSimilarLines(true);
const auto container = box->verticalLayout();
const auto sub = st::boxRowPadding - st::defaultSubsectionTitlePadding;
const auto subtitlePadding = QMargins(sub.left(), 0, sub.right(), 0);
AddSubsectionTitle(
container,
tr::lng_stake_game_results(),
subtitlePadding);
AddSubsectionTitle(container, tr::lng_stake_game_results());
const auto half = (st::defaultTable.border + 1) / 2;
box->addRow(
MakeTable(box, args),
st::boxRowPadding + QMargins(-half, 0, -half, skip / 2),
style::al_top);
const auto six = Lottie::IconDescriptor{
.name = u"dice_6"_q,
.sizeOverride = { st::emojiSize, st::emojiSize },
};
const auto sixText = Text::LottieEmoji(six);
const auto sixContext = Text::LottieEmojiContext(six);
const auto factory = [=](
QStringView data,
const Text::MarkedContext &context) {
if (auto result = sixContext.customEmojiFactory(data, context)) {
return std::make_unique<Text::LimitedLoopsEmoji>(
std::move(result),
0,
true);
}
return std::unique_ptr<Text::LimitedLoopsEmoji>();
};
const auto context = Text::MarkedContext{
.customEmojiFactory = factory,
};
const auto resets = box->addRow(
object_ptr<FlatLabel>(
box,
tr::lng_stake_game_resets(
lt_emoji,
rpl::single(sixText),
tr::rich),
st::confcallLinkFooterOr,
st::defaultPopupMenu,
context),
st::boxRowPadding,
style::al_top);
resets->setTryMakeSimilarLines(true);
resets->setAnimationsPausedCallback([=] {
return FlatLabel::WhichAnimationsPaused::All;
});
AddSubsectionTitle(
container,
tr::lng_stake_game_your(),
subtitlePadding + QMargins(0, skip, 0, sub.bottom()));
const auto field = AddTonInputField(container, {
.value = args.currentStake,
});
box->setFocusCallback([=] {
field->setFocusFast();
});
const auto submit = args.submit;
const auto callback = [=] {
const auto text = field->getLastText();
const auto now = Ui::ParseTonAmountString(text);
if (!now) {
field->showError();
} else {
box->closeBox();
submit(*now);
}
};
field->submits() | rpl::on_next(callback, field->lifetime());
const auto button = box->addButton(rpl::single(QString()), callback);
button->setText(tr::lng_stake_game_save_and_roll(
) | rpl::map([=](QString text) {
return tr::marked(
QString::fromUtf8("\xf0\x9f\x8e\xb2")
).append(' ').append(text);
}));
const auto close = CreateChild<IconButton>(
container,
@@ -186,39 +397,6 @@ void EmojiGameStakeBox(
st::creditsHistoryRightSkip);
balance->update();
}, balance->lifetime());
box->addRow(
object_ptr<FlatLabel>(
box,
tr::lng_stake_game_resets(tr::rich),
st::confcallLinkCenteredText),
st::boxRowPadding,
style::al_top
)->setTryMakeSimilarLines(true);
AddSubsectionTitle(container, tr::lng_stake_game_your());
const auto field = AddTonInputField(container, {
.value = args.currentStake,
});
const auto submit = args.submit;
const auto button = box->addButton(rpl::single(QString()), [=] {
const auto text = field->getLastText();
const auto now = Ui::ParseTonAmountString(text);
if (!now) {
field->showError();
} else {
box->closeBox();
submit(*now);
}
});
button->setText(tr::lng_stake_game_save_and_roll(
) | rpl::map([=](QString text) {
return tr::marked(
QString::fromUtf8("\xf0\x9f\x8e\xb2")
).append(' ').append(text);
}));
}
} // namespace Ui