mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Implement unique gift view in chat.
This commit is contained in:
parent
13d2f70c3a
commit
2d53ec5d34
19 changed files with 736 additions and 55 deletions
|
@ -799,6 +799,8 @@ PRIVATE
|
|||
history/view/media/history_view_story_mention.h
|
||||
history/view/media/history_view_theme_document.cpp
|
||||
history/view/media/history_view_theme_document.h
|
||||
history/view/media/history_view_unique_gift.cpp
|
||||
history/view/media/history_view_unique_gift.h
|
||||
history/view/media/history_view_userpic_suggestion.cpp
|
||||
history/view/media/history_view_userpic_suggestion.h
|
||||
history/view/media/history_view_web_page.cpp
|
||||
|
|
|
@ -774,7 +774,7 @@ std::optional<Data::StarGift> FromTL(
|
|||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.starsUpgraded = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
|
@ -849,7 +849,8 @@ std::optional<Data::UserStarGift> FromTL(
|
|||
}
|
||||
: TextWithEntities()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsUpgraded = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.starsUpgradedBySender = int64(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
|
|
|
@ -212,6 +212,7 @@ auto GenerateGiftMedia(
|
|||
push(std::make_unique<MediaGenericTextPart>(
|
||||
std::move(text),
|
||||
margins,
|
||||
st::defaultTextStyle,
|
||||
links,
|
||||
context));
|
||||
};
|
||||
|
@ -276,9 +277,9 @@ struct PatternPoint {
|
|||
};
|
||||
[[nodiscard]] const std::vector<PatternPoint> &PatternPoints() {
|
||||
static const auto kSmall = 0.7;
|
||||
static const auto kFaded = 0.7;
|
||||
static const auto kFaded = 0.5;
|
||||
static const auto kLarge = 0.85;
|
||||
static const auto kOpaque = 0.9;
|
||||
static const auto kOpaque = 0.7;
|
||||
static const auto result = std::vector<PatternPoint>{
|
||||
{ { 0.5, 0.066 }, kSmall, kFaded },
|
||||
|
||||
|
@ -1088,10 +1089,6 @@ void SendGift(
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ComputeTitle(const Data::UniqueGift &gift) {
|
||||
return gift.title + u" #"_q + QString::number(gift.number);
|
||||
}
|
||||
|
||||
void SendUpgradeRequest(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Settings::SmallBalanceResult result,
|
||||
|
@ -1117,7 +1114,7 @@ void SendUpgradeRequest(
|
|||
.text = tr::lng_gift_upgraded_about(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Text::Bold(ComputeTitle(*gift)),
|
||||
Text::Bold(Data::UniqueGiftName(*gift)),
|
||||
Ui::Text::WithEntities),
|
||||
});
|
||||
}
|
||||
|
@ -1159,7 +1156,7 @@ void UpgradeGift(
|
|||
.text = tr::lng_gift_upgraded_about(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Text::Bold(ComputeTitle(*gift)),
|
||||
Text::Bold(Data::UniqueGiftName(*gift)),
|
||||
Ui::Text::WithEntities),
|
||||
});
|
||||
}
|
||||
|
@ -1846,32 +1843,14 @@ void AddUniqueGiftCover(
|
|||
gift.gradient = CreateGradient(cover->size(), *gift.gift);
|
||||
}
|
||||
p.drawImage(0, 0, gift.gradient);
|
||||
const auto paintPoint = [&](const PatternPoint &point) {
|
||||
const auto key = (1. + point.opacity) * 10. + point.scale;
|
||||
auto &image = gift.emojis[key];
|
||||
PrepareImage(image, gift.emoji.get(), point, *gift.gift);
|
||||
if (!image.isNull()) {
|
||||
const auto x = int(point.position.x() * width);
|
||||
const auto y = int(point.position.y() * pointsHeight);
|
||||
if (shown < 1.) {
|
||||
p.save();
|
||||
p.translate(x, y);
|
||||
p.scale(shown, shown);
|
||||
p.translate(-x, -y);
|
||||
}
|
||||
const auto size = image.size() / ratio;
|
||||
p.drawImage(
|
||||
x - size.width() / 2,
|
||||
y - size.height() / 2,
|
||||
image);
|
||||
if (shown < 1.) {
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto point : PatternPoints()) {
|
||||
paintPoint(point);
|
||||
}
|
||||
|
||||
PaintPoints(
|
||||
p,
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
shown);
|
||||
|
||||
const auto lottie = gift.lottie.get();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
|
@ -2095,6 +2074,45 @@ void UpgradeBox(
|
|||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void PaintPoints(
|
||||
QPainter &p,
|
||||
base::flat_map<float64, QImage> &cache,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const Data::UniqueGift &gift,
|
||||
const QRect &rect,
|
||||
float64 shown) {
|
||||
const auto origin = rect.topLeft();
|
||||
const auto width = rect.width();
|
||||
const auto height = rect.height();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto paintPoint = [&](const PatternPoint &point) {
|
||||
const auto key = (1. + point.opacity) * 10. + point.scale;
|
||||
auto &image = cache[key];
|
||||
PrepareImage(image, emoji, point, gift);
|
||||
if (!image.isNull()) {
|
||||
const auto position = origin + QPoint(
|
||||
int(point.position.x() * width),
|
||||
int(point.position.y() * height));
|
||||
if (shown < 1.) {
|
||||
p.save();
|
||||
p.translate(position);
|
||||
p.scale(shown, shown);
|
||||
p.translate(-position);
|
||||
}
|
||||
const auto size = image.size() / ratio;
|
||||
p.drawImage(
|
||||
position - QPoint(size.width() / 2, size.height() / 2),
|
||||
image);
|
||||
if (shown < 1.) {
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto point : PatternPoints()) {
|
||||
paintPoint(point);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowStarGiftUpgradeBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
uint64 stargiftId,
|
||||
|
|
|
@ -15,6 +15,10 @@ namespace Window {
|
|||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class VerticalLayout;
|
||||
|
@ -31,6 +35,14 @@ void AddUniqueGiftCover(
|
|||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr);
|
||||
|
||||
void PaintPoints(
|
||||
QPainter &p,
|
||||
base::flat_map<float64, QImage> &cache,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const Data::UniqueGift &gift,
|
||||
const QRect &rect,
|
||||
float64 shown = 1.);
|
||||
|
||||
void ShowStarGiftUpgradeBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
uint64 stargiftId,
|
||||
|
|
|
@ -77,7 +77,8 @@ struct CreditsHistoryEntry final {
|
|||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int starsConverted = 0;
|
||||
int starsUpgraded = 0;
|
||||
int starsToUpgrade = 0;
|
||||
int starsUpgradedBySender = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
|
|
|
@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/media/history_view_service_box.h"
|
||||
#include "history/view/media/history_view_story_mention.h"
|
||||
#include "history/view/media/history_view_premium_gift.h"
|
||||
#include "history/view/media/history_view_unique_gift.h"
|
||||
#include "history/view/media/history_view_userpic_suggestion.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
|
@ -2374,6 +2375,16 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
|||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
if (const auto raw = _data.unique.get()) {
|
||||
return std::make_unique<HistoryView::MediaGeneric>(
|
||||
message,
|
||||
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw),
|
||||
HistoryView::MediaGenericDescriptor{
|
||||
.maxWidth = st::chatIntroWidth,
|
||||
.paintBg = HistoryView::UniqueGiftBg(message, raw),
|
||||
.service = true,
|
||||
});
|
||||
}
|
||||
return std::make_unique<HistoryView::ServiceBox>(
|
||||
message,
|
||||
std::make_unique<HistoryView::PremiumGift>(message, this));
|
||||
|
|
|
@ -132,6 +132,7 @@ enum class GiftType : uchar {
|
|||
Premium, // count - months
|
||||
Credits, // count - credits
|
||||
StarGift, // count - stars
|
||||
StarGiftUpgrade,
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
|
@ -143,7 +144,8 @@ struct GiftCode {
|
|||
ChannelData *channel = nullptr;
|
||||
MsgId giveawayMsgId = 0;
|
||||
int starsConverted = 0;
|
||||
int starsUpgraded = 0;
|
||||
int starsToUpgrade = 0;
|
||||
int starsUpgradedBySender = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int count = 0;
|
||||
|
|
|
@ -46,12 +46,16 @@ struct UniqueGift {
|
|||
UniqueGiftOriginalDetails originalDetails;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline QString UniqueGiftName(const UniqueGift &gift) {
|
||||
return gift.title + u" #"_q + QString::number(gift.number);
|
||||
}
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
std::shared_ptr<UniqueGift> unique;
|
||||
int64 stars = 0;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsUpgraded = 0;
|
||||
int64 starsToUpgrade = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
|
@ -69,7 +73,7 @@ struct UserStarGift {
|
|||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsUpgraded = 0;
|
||||
int64 starsUpgradedBySender = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
|
|
|
@ -5657,7 +5657,8 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||
}
|
||||
: TextWithEntities()),
|
||||
.starsConverted = int(data.vconvert_stars().value_or_empty()),
|
||||
.starsUpgraded = int(data.vupgrade_stars().value_or_empty()),
|
||||
.starsUpgradedBySender = int(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.type = Data::GiftType::StarGift,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
|
@ -5667,6 +5668,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||
};
|
||||
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
|
||||
fields.stargiftId = gift->id;
|
||||
fields.starsToUpgrade = gift->starsToUpgrade;
|
||||
fields.document = gift->document;
|
||||
fields.limitedCount = gift->limitedCount;
|
||||
fields.limitedLeft = gift->limitedLeft;
|
||||
|
@ -5680,7 +5682,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||
}, [&](const MTPDmessageActionStarGiftUnique &data) {
|
||||
using Fields = Data::GiftCode;
|
||||
auto fields = Fields{
|
||||
.type = Data::GiftType::StarGift,
|
||||
.type = Data::GiftType::StarGiftUpgrade,
|
||||
.saved = data.is_saved(),
|
||||
};
|
||||
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
|
||||
|
|
|
@ -94,6 +94,7 @@ auto GenerateChatIntro(
|
|||
push(std::make_unique<MediaGenericTextPart>(
|
||||
std::move(text),
|
||||
margins,
|
||||
st::defaultTextStyle,
|
||||
links));
|
||||
};
|
||||
const auto title = data.customPhrases()
|
||||
|
|
|
@ -74,6 +74,7 @@ auto GenerateGiveawayStart(
|
|||
push(std::make_unique<MediaGenericTextPart>(
|
||||
std::move(text),
|
||||
margins,
|
||||
st::defaultTextStyle,
|
||||
links));
|
||||
};
|
||||
pushText(
|
||||
|
@ -242,6 +243,7 @@ auto GenerateGiveawayResults(
|
|||
push(std::make_unique<MediaGenericTextPart>(
|
||||
std::move(text),
|
||||
margins,
|
||||
st::defaultTextStyle,
|
||||
links));
|
||||
};
|
||||
const auto isSingleWinner = (data->winnersCount == 1);
|
||||
|
|
|
@ -74,6 +74,7 @@ MediaGeneric::MediaGeneric(
|
|||
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
|
||||
MediaGenericDescriptor &&descriptor)
|
||||
: Media(parent)
|
||||
, _paintBg(std::move(descriptor.paintBg))
|
||||
, _maxWidthCap(descriptor.maxWidth)
|
||||
, _service(descriptor.service)
|
||||
, _hideServiceText(descriptor.hideServiceText) {
|
||||
|
@ -123,6 +124,8 @@ void MediaGeneric::draw(Painter &p, const PaintContext &context) const {
|
|||
const auto outer = width();
|
||||
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||
return;
|
||||
} else if (_paintBg) {
|
||||
_paintBg(p, context);
|
||||
} else if (_service) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
const auto radius = st::msgServiceGiftBoxRadius;
|
||||
|
@ -229,12 +232,13 @@ QMargins MediaGeneric::inBubblePadding() const {
|
|||
MediaGenericTextPart::MediaGenericTextPart(
|
||||
TextWithEntities text,
|
||||
QMargins margins,
|
||||
const style::TextStyle &st,
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links,
|
||||
const std::any &context)
|
||||
: _text(st::msgMinWidth)
|
||||
, _margins(margins) {
|
||||
_text.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
st,
|
||||
text,
|
||||
kMarkupTextOptions,
|
||||
context);
|
||||
|
@ -248,16 +252,13 @@ void MediaGenericTextPart::draw(
|
|||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const {
|
||||
const auto service = owner->service();
|
||||
p.setPen(service
|
||||
? context.st->msgServiceFg()
|
||||
: context.messageStyle()->historyTextFg);
|
||||
setupPen(p, owner, context);
|
||||
_text.draw(p, {
|
||||
.position = { (outerWidth - width()) / 2, _margins.top() },
|
||||
.outerWidth = outerWidth,
|
||||
.availableWidth = width(),
|
||||
.align = style::al_top,
|
||||
.palette = &(service
|
||||
.palette = &(owner->service()
|
||||
? context.st->serviceTextPalette()
|
||||
: context.messageStyle()->textPalette),
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
|
@ -267,6 +268,16 @@ void MediaGenericTextPart::draw(
|
|||
});
|
||||
}
|
||||
|
||||
void MediaGenericTextPart::setupPen(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context) const {
|
||||
const auto service = owner->service();
|
||||
p.setPen(service
|
||||
? context.st->msgServiceFg()
|
||||
: context.messageStyle()->historyTextFg);
|
||||
}
|
||||
|
||||
TextState MediaGenericTextPart::textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
|
|
|
@ -15,6 +15,14 @@ class DynamicImage;
|
|||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace style {
|
||||
struct TextStyle;
|
||||
} // namespace style
|
||||
|
||||
namespace st {
|
||||
extern const style::TextStyle &defaultTextStyle;
|
||||
} // namespace st
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class MediaGeneric;
|
||||
|
@ -45,6 +53,7 @@ public:
|
|||
|
||||
struct MediaGenericDescriptor {
|
||||
int maxWidth = 0;
|
||||
Fn<void(Painter&, const PaintContext&)> paintBg;
|
||||
ClickHandlerPtr serviceLink;
|
||||
bool service = false;
|
||||
bool hideServiceText = false;
|
||||
|
@ -110,17 +119,19 @@ private:
|
|||
[[nodiscard]] QMargins inBubblePadding() const;
|
||||
|
||||
std::vector<Entry> _entries;
|
||||
Fn<void(Painter&, const PaintContext&)> _paintBg;
|
||||
int _maxWidthCap = 0;
|
||||
bool _service : 1 = false;
|
||||
bool _hideServiceText : 1 = false;
|
||||
|
||||
};
|
||||
|
||||
class MediaGenericTextPart final : public MediaGenericPart {
|
||||
class MediaGenericTextPart : public MediaGenericPart {
|
||||
public:
|
||||
MediaGenericTextPart(
|
||||
TextWithEntities text,
|
||||
QMargins margins,
|
||||
const style::TextStyle &st = st::defaultTextStyle,
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
|
||||
const std::any &context = {});
|
||||
|
||||
|
@ -137,6 +148,12 @@ public:
|
|||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
protected:
|
||||
virtual void setupPen(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context) const;
|
||||
|
||||
private:
|
||||
Ui::Text::String _text;
|
||||
QMargins _margins;
|
||||
|
|
|
@ -266,11 +266,13 @@ void PremiumGift::unloadHeavyPart() {
|
|||
}
|
||||
|
||||
bool PremiumGift::incomingGift() const {
|
||||
return gift() && !_parent->data()->out();
|
||||
const auto out = _parent->data()->out();
|
||||
return gift() && (starGiftUpgrade() ? out : !out);
|
||||
}
|
||||
|
||||
bool PremiumGift::outgoingGift() const {
|
||||
return gift() && _parent->data()->out();
|
||||
const auto out = _parent->data()->out();
|
||||
return gift() && (starGiftUpgrade() ? !out : out);
|
||||
}
|
||||
|
||||
bool PremiumGift::gift() const {
|
||||
|
@ -278,7 +280,12 @@ bool PremiumGift::gift() const {
|
|||
}
|
||||
|
||||
bool PremiumGift::starGift() const {
|
||||
return _data.type == Data::GiftType::StarGift;
|
||||
return (_data.type == Data::GiftType::StarGift)
|
||||
|| (_data.type == Data::GiftType::StarGiftUpgrade);
|
||||
}
|
||||
|
||||
bool PremiumGift::starGiftUpgrade() const {
|
||||
return (_data.type == Data::GiftType::StarGiftUpgrade);
|
||||
}
|
||||
|
||||
bool PremiumGift::creditsPrize() const {
|
||||
|
|
|
@ -51,6 +51,7 @@ private:
|
|||
[[nodiscard]] bool incomingGift() const;
|
||||
[[nodiscard]] bool outgoingGift() const;
|
||||
[[nodiscard]] bool starGift() const;
|
||||
[[nodiscard]] bool starGiftUpgrade() const;
|
||||
[[nodiscard]] bool gift() const;
|
||||
[[nodiscard]] bool creditsPrize() const;
|
||||
[[nodiscard]] int credits() const;
|
||||
|
|
|
@ -0,0 +1,539 @@
|
|||
/*
|
||||
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 "history/view/media/history_view_unique_gift.h"
|
||||
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "history/view/media/history_view_media_generic.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/rect.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_credits.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
class TextPartColored final : public MediaGenericTextPart {
|
||||
public:
|
||||
TextPartColored(
|
||||
TextWithEntities text,
|
||||
QMargins margins,
|
||||
QColor color,
|
||||
const style::TextStyle &st = st::defaultTextStyle,
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
|
||||
const std::any &context = {});
|
||||
|
||||
private:
|
||||
void setupPen(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context) const override;
|
||||
|
||||
QColor _color;
|
||||
|
||||
};
|
||||
|
||||
class AttributeTable final : public MediaGenericPart {
|
||||
public:
|
||||
struct Entry {
|
||||
QString label;
|
||||
QString value;
|
||||
};
|
||||
|
||||
AttributeTable(
|
||||
std::vector<Entry> entries,
|
||||
QMargins margins,
|
||||
QColor labelColor);
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
struct Part {
|
||||
Ui::Text::String label;
|
||||
Ui::Text::String value;
|
||||
};
|
||||
|
||||
std::vector<Part> _parts;
|
||||
QMargins _margins;
|
||||
QColor _labelColor;
|
||||
int _valueLeft = 0;
|
||||
|
||||
};
|
||||
|
||||
class ButtonPart final : public MediaGenericPart {
|
||||
public:
|
||||
ButtonPart(
|
||||
const QString &text,
|
||||
QMargins margins,
|
||||
QColor bg,
|
||||
Fn<void()> repaint,
|
||||
ClickHandlerPtr link);
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
TextState textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
int outerWidth) const override;
|
||||
|
||||
void clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed) override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
Ui::Text::String _text;
|
||||
QMargins _margins;
|
||||
QColor _bg;
|
||||
QSize _size;
|
||||
|
||||
ClickHandlerPtr _link;
|
||||
std::unique_ptr<Ui::RippleAnimation> _ripple;
|
||||
mutable Ui::Premium::ColoredMiniStars _stars;
|
||||
Fn<void()> _repaint;
|
||||
|
||||
mutable QPoint _lastPoint;
|
||||
|
||||
};
|
||||
|
||||
ButtonPart::ButtonPart(
|
||||
const QString &text,
|
||||
QMargins margins,
|
||||
QColor bg,
|
||||
Fn<void()> repaint,
|
||||
ClickHandlerPtr link)
|
||||
: _text(st::semiboldTextStyle, text)
|
||||
, _margins(margins)
|
||||
, _bg(bg)
|
||||
, _size(
|
||||
(_text.maxWidth()
|
||||
+ st::msgServiceGiftBoxButtonHeight
|
||||
+ st::msgServiceGiftBoxButtonPadding.left()
|
||||
+ st::msgServiceGiftBoxButtonPadding.right()),
|
||||
st::msgServiceGiftBoxButtonHeight)
|
||||
, _link(std::move(link))
|
||||
, _stars([=](const QRect &) {
|
||||
repaint();
|
||||
}, Ui::Premium::MiniStars::Type::SlowStars)
|
||||
, _repaint(std::move(repaint)) {
|
||||
_stars.setColorOverride(QGradientStops{
|
||||
{ 0., QColor(255, 255, 255, 255 * .3) },
|
||||
{ 1., QColor(255, 255, 255) },
|
||||
});
|
||||
const auto padding = _size.height() / 2;
|
||||
_stars.setCenter(
|
||||
Rect(_size) - QMargins(padding, 0, padding, 0));
|
||||
}
|
||||
|
||||
void ButtonPart::draw(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
const auto position = QPoint(
|
||||
(outerWidth - width()) / 2 + _margins.left(),
|
||||
_margins.top());
|
||||
p.translate(position);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_bg);
|
||||
const auto radius = _size.height() / 2.;
|
||||
const auto r = Rect(_size);
|
||||
p.drawRoundedRect(r, radius, radius);
|
||||
|
||||
auto clipPath = QPainterPath();
|
||||
clipPath.addRoundedRect(r, radius, radius);
|
||||
p.setClipPath(clipPath);
|
||||
_stars.paint(p);
|
||||
p.setClipping(false);
|
||||
|
||||
auto white = QColor(255, 255, 255);
|
||||
p.setPen(white);
|
||||
if (_ripple) {
|
||||
const auto opacity = p.opacity();
|
||||
p.setOpacity(st::historyPollRippleOpacity);
|
||||
white.setAlphaF(0.3);
|
||||
_ripple->paint(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
&white);
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
_text.draw(
|
||||
p,
|
||||
0,
|
||||
(_size.height() - _text.minHeight()) / 2,
|
||||
_size.width(),
|
||||
style::al_top);
|
||||
|
||||
p.translate(-position);
|
||||
}
|
||||
|
||||
TextState ButtonPart::textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
int outerWidth) const {
|
||||
point -= QPoint{
|
||||
(outerWidth - width()) / 2 + _margins.left(),
|
||||
_margins.top()
|
||||
};
|
||||
if (QRect(QPoint(), _size).contains(point)) {
|
||||
auto result = TextState();
|
||||
result.link = _link;
|
||||
_lastPoint = point;
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ButtonPart::clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed) {
|
||||
if (p != _link) {
|
||||
return;
|
||||
} else if (pressed) {
|
||||
if (!_ripple) {
|
||||
const auto radius = _size.height() / 2;
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
Ui::RippleAnimation::RoundRectMask(_size, radius),
|
||||
_repaint);
|
||||
}
|
||||
_ripple->add(_lastPoint);
|
||||
} else if (_ripple) {
|
||||
_ripple->lastStop();
|
||||
}
|
||||
}
|
||||
|
||||
QSize ButtonPart::countOptimalSize() {
|
||||
return {
|
||||
_margins.left() + _size.width() + _margins.right(),
|
||||
_margins.top() + _size.height() + _margins.bottom(),
|
||||
};
|
||||
}
|
||||
|
||||
QSize ButtonPart::countCurrentSize(int newWidth) {
|
||||
return optimalSize();
|
||||
}
|
||||
|
||||
TextPartColored::TextPartColored(
|
||||
TextWithEntities text,
|
||||
QMargins margins,
|
||||
QColor color,
|
||||
const style::TextStyle &st,
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links,
|
||||
const std::any &context)
|
||||
: MediaGenericTextPart(text, margins, st, links, context)
|
||||
, _color(color) {
|
||||
}
|
||||
|
||||
void TextPartColored::setupPen(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context) const {
|
||||
p.setPen(_color);
|
||||
}
|
||||
|
||||
AttributeTable::AttributeTable(
|
||||
std::vector<Entry> entries,
|
||||
QMargins margins,
|
||||
QColor labelColor)
|
||||
: _margins(margins)
|
||||
, _labelColor(labelColor) {
|
||||
for (const auto &entry : entries) {
|
||||
_parts.emplace_back();
|
||||
auto &part = _parts.back();
|
||||
part.label.setText(st::defaultTextStyle, entry.label);
|
||||
part.value.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
Ui::Text::Bold(entry.value));
|
||||
}
|
||||
}
|
||||
|
||||
void AttributeTable::draw(
|
||||
Painter &p,
|
||||
not_null<const MediaGeneric*> owner,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const {
|
||||
const auto labelRight = _valueLeft - st::chatUniqueTableSkip;
|
||||
const auto palette = &context.st->serviceTextPalette();
|
||||
auto top = _margins.top();
|
||||
const auto paint = [&](
|
||||
const Ui::Text::String &text,
|
||||
int left,
|
||||
int availableWidth,
|
||||
style::align align) {
|
||||
text.draw(p, {
|
||||
.position = { left, top },
|
||||
.outerWidth = outerWidth,
|
||||
.availableWidth = availableWidth,
|
||||
.align = align,
|
||||
.palette = palette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = context.now,
|
||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||
.elisionLines = 1,
|
||||
});
|
||||
};
|
||||
const auto forLabel = labelRight - _margins.left();
|
||||
const auto forValue = width() - _valueLeft - _margins.right();
|
||||
const auto white = QColor(255, 255, 255);
|
||||
for (const auto &part : _parts) {
|
||||
p.setPen(_labelColor);
|
||||
paint(part.label, _margins.left(), forLabel, style::al_topright);
|
||||
p.setPen(white);
|
||||
paint(part.value, _valueLeft, forValue, style::al_topleft);
|
||||
top += st::normalFont->height + st::chatUniqueRowSkip;
|
||||
}
|
||||
}
|
||||
|
||||
QSize AttributeTable::countOptimalSize() {
|
||||
auto maxLabel = 0;
|
||||
auto maxValue = 0;
|
||||
for (const auto &part : _parts) {
|
||||
maxLabel = std::max(maxLabel, part.label.maxWidth());
|
||||
maxValue = std::max(maxValue, part.value.maxWidth());
|
||||
}
|
||||
const auto skip = st::chatUniqueTableSkip;
|
||||
const auto row = st::normalFont->height + st::chatUniqueRowSkip;
|
||||
const auto height = int(_parts.size()) * row - st::chatUniqueRowSkip;
|
||||
return {
|
||||
_margins.left() + maxLabel + skip + maxValue + _margins.right(),
|
||||
_margins.top() + height + _margins.bottom(),
|
||||
};
|
||||
}
|
||||
|
||||
QSize AttributeTable::countCurrentSize(int newWidth) {
|
||||
const auto skip = st::chatUniqueTableSkip;
|
||||
const auto width = newWidth - _margins.left() - _margins.right() - skip;
|
||||
auto maxLabel = 0;
|
||||
auto maxValue = 0;
|
||||
for (const auto &part : _parts) {
|
||||
maxLabel = std::max(maxLabel, part.label.maxWidth());
|
||||
maxValue = std::max(maxValue, part.value.maxWidth());
|
||||
}
|
||||
if (width <= 0 || !maxLabel) {
|
||||
_valueLeft = _margins.left();
|
||||
} else if (!maxValue) {
|
||||
_valueLeft = newWidth - _margins.right();
|
||||
} else {
|
||||
_valueLeft = _margins.left()
|
||||
+ int((int64(maxLabel) * width) / (maxLabel + maxValue))
|
||||
+ skip;
|
||||
}
|
||||
return { newWidth, minHeight() };
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
auto GenerateUniqueGiftMedia(
|
||||
not_null<Element*> parent,
|
||||
Element *replacing,
|
||||
not_null<Data::UniqueGift*> gift)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
|
||||
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
|
||||
auto pushText = [&](
|
||||
TextWithEntities text,
|
||||
const style::TextStyle &st,
|
||||
QColor color,
|
||||
QMargins margins) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
push(std::make_unique<TextPartColored>(
|
||||
std::move(text),
|
||||
margins,
|
||||
color,
|
||||
st));
|
||||
};
|
||||
|
||||
const auto white = QColor(255, 255, 255);
|
||||
const auto sticker = [=] {
|
||||
using Tag = ChatHelpers::StickerLottieSize;
|
||||
return StickerInBubblePart::Data{
|
||||
.sticker = gift->model.document,
|
||||
.size = st::chatIntroStickerSize,
|
||||
.cacheTag = Tag::ChatIntroHelloSticker,
|
||||
};
|
||||
};
|
||||
push(std::make_unique<StickerInBubblePart>(
|
||||
parent,
|
||||
replacing,
|
||||
sticker,
|
||||
st::chatUniqueStickerPadding));
|
||||
pushText(
|
||||
Ui::Text::Bold((!parent->data()->out()
|
||||
? tr::lng_action_gift_sent_subtitle
|
||||
: tr::lng_action_gift_got_subtitle)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
parent->history()->peer->shortName())),
|
||||
st::chatUniqueTitle,
|
||||
white,
|
||||
st::chatUniqueTitlePadding);
|
||||
pushText(
|
||||
Ui::Text::Bold(Data::UniqueGiftName(*gift)),
|
||||
st::defaultTextStyle,
|
||||
gift->backdrop.textColor,
|
||||
st::chatUniqueTextPadding);
|
||||
|
||||
const auto withButton = parent->data()->out();
|
||||
|
||||
auto attributes = std::vector<AttributeTable::Entry>{
|
||||
{ tr::lng_gift_unique_model(tr::now), gift->model.name },
|
||||
{ tr::lng_gift_unique_backdrop(tr::now), gift->backdrop.name },
|
||||
{ tr::lng_gift_unique_symbol(tr::now), gift->pattern.name },
|
||||
};
|
||||
push(std::make_unique<AttributeTable>(
|
||||
std::move(attributes),
|
||||
(withButton
|
||||
? st::chatUniqueTextPadding
|
||||
: st::chatUniqueTableAtBottomPadding),
|
||||
gift->backdrop.textColor));
|
||||
|
||||
if (withButton) {
|
||||
const auto itemId = parent->data()->fullId();
|
||||
auto link = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto owner = &controller->session().data();
|
||||
if (const auto item = owner->message(itemId)) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto gift = media->gift()) {
|
||||
controller->show(Box(
|
||||
Settings::StarGiftViewBox,
|
||||
controller,
|
||||
*gift,
|
||||
item));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
push(std::make_unique<ButtonPart>(
|
||||
tr::lng_sticker_premium_view(tr::now),
|
||||
st::chatUniqueButtonPadding,
|
||||
gift->backdrop.patternColor,
|
||||
[=] { parent->repaint(); },
|
||||
std::move(link)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
|
||||
not_null<Element*> view,
|
||||
not_null<Data::UniqueGift*> gift) {
|
||||
struct State {
|
||||
QImage bg;
|
||||
base::flat_map<float64, QImage> cache;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> pattern;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
state->pattern = view->history()->owner().customEmojiManager().create(
|
||||
gift->pattern.document,
|
||||
[=] { view->repaint(); },
|
||||
Data::CustomEmojiSizeTag::Large);
|
||||
[[maybe_unused]] const auto preload = state->pattern->ready();
|
||||
|
||||
return [=](Painter &p, const Ui::ChatPaintContext &context) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto thickness = st::chatUniqueGiftBorder * 2;
|
||||
auto pen = context.st->msgServiceBg()->p;
|
||||
pen.setWidthF(thickness);
|
||||
p.setPen(pen);
|
||||
p.setBrush(Qt::transparent);
|
||||
const auto radius = st::msgServiceGiftBoxRadius - thickness;
|
||||
const auto media = view->media();
|
||||
const auto full = QRect(0, 0, media->width(), media->height());
|
||||
const auto inner = full.marginsRemoved(
|
||||
{ thickness, thickness, thickness, thickness });
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
auto gradient = QRadialGradient(
|
||||
inner.center(),
|
||||
inner.height() / 2);
|
||||
gradient.setStops({
|
||||
{ 0., gift->backdrop.centerColor },
|
||||
{ 1., gift->backdrop.edgeColor },
|
||||
});
|
||||
p.setBrush(gradient);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
|
||||
const auto width = media->width();
|
||||
const auto shift = width / 12;
|
||||
const auto doubled = width + 2 * shift;
|
||||
const auto outer = QRect(-shift, -shift, doubled, doubled);
|
||||
p.setClipRect(inner);
|
||||
Ui::PaintPoints(p, state->cache, state->pattern.get(), *gift, outer);
|
||||
p.setClipping(false);
|
||||
|
||||
p.save();
|
||||
p.translate(inner.topLeft());
|
||||
const auto tag = tr::lng_gift_limited_of_one(tr::now);
|
||||
const auto font = st::semiboldFont;
|
||||
p.setFont(font);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto twidth = font->width(tag);
|
||||
const auto pos = QPoint(inner.width() - twidth, font->height);
|
||||
const auto add = style::ConvertScale(2);
|
||||
p.setClipRect(
|
||||
-add,
|
||||
-add,
|
||||
inner.width() + 2 * add,
|
||||
inner.height() + 2 * add);
|
||||
p.translate(pos);
|
||||
p.rotate(45.);
|
||||
p.translate(-pos);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gift->backdrop.patternColor);
|
||||
p.drawRect(-5 * twidth, 0, twidth * 12, font->height);
|
||||
p.setPen(gift->backdrop.textColor);
|
||||
p.drawText(pos - QPoint(0, font->descent), tag);
|
||||
p.restore();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Data {
|
||||
class MediaGiftBox;
|
||||
struct UniqueGift;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
struct ChatPaintContext;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
class MediaGenericPart;
|
||||
|
||||
auto GenerateUniqueGiftMedia(
|
||||
not_null<Element*> parent,
|
||||
Element *replacing,
|
||||
not_null<Data::UniqueGift*> gift)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
|
||||
|
||||
Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
|
||||
not_null<Element*> view,
|
||||
not_null<Data::UniqueGift*> gift);
|
||||
|
||||
} // namespace HistoryView
|
|
@ -1342,7 +1342,7 @@ void ReceiptCreditsBox(
|
|||
e.stargiftId,
|
||||
starGiftSender,
|
||||
itemId,
|
||||
e.starsUpgraded,
|
||||
e.starsUpgradedBySender ? 0 : e.starsToUpgrade,
|
||||
[=](bool) { *upgradeGuard = false; });
|
||||
}
|
||||
};
|
||||
|
@ -1611,7 +1611,8 @@ void UserStarGiftBox(
|
|||
.limitedCount = data.info.limitedCount,
|
||||
.limitedLeft = data.info.limitedLeft,
|
||||
.starsConverted = int(data.info.starsConverted),
|
||||
.starsUpgraded = int(data.starsUpgraded),
|
||||
.starsToUpgrade = int(data.info.starsToUpgrade),
|
||||
.starsUpgradedBySender = int(data.starsUpgradedBySender),
|
||||
.converted = false,
|
||||
.anonymous = data.anonymous,
|
||||
.stargift = true,
|
||||
|
@ -1645,7 +1646,8 @@ void StarGiftViewBox(
|
|||
.limitedCount = data.limitedCount,
|
||||
.limitedLeft = data.limitedLeft,
|
||||
.starsConverted = data.starsConverted,
|
||||
.starsUpgraded = data.starsUpgraded,
|
||||
.starsToUpgrade = data.starsToUpgrade,
|
||||
.starsUpgradedBySender = data.starsUpgradedBySender,
|
||||
.converted = data.converted,
|
||||
.anonymous = data.anonymous,
|
||||
.stargift = true,
|
||||
|
|
|
@ -1205,3 +1205,15 @@ botDownloadCancel: IconButton {
|
|||
rippleAreaSize: 20px;
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
chatUniqueGiftBorder: 4px;
|
||||
|
||||
chatUniqueStickerPadding: margins(10px, 12px, 10px, 8px);
|
||||
chatUniqueTitle: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
}
|
||||
chatUniqueTitlePadding: margins(12px, 4px, 12px, 2px);
|
||||
chatUniqueTextPadding: margins(12px, 2px, 12px, 8px);
|
||||
chatUniqueTableSkip: 10px;
|
||||
chatUniqueTableAtBottomPadding: margins(12px, 2px, 12px, 20px);
|
||||
chatUniqueRowSkip: 4px;
|
||||
chatUniqueButtonPadding: margins(12px, 4px, 12px, 16px);
|
||||
|
|
Loading…
Add table
Reference in a new issue